node使用promise替代回调函数
在学习Node.js过程中接触到了如何使用async来控制并发(使用async控制并发)
async的本质是一个流程控制。其实在异步编程中,还有一个更为经典的模型,叫做Promise/Deferred模型(当然还有更多相关解决方法,比如eventproxy,co等,到时候遇到在挖坑)
首先,我们思考一个典型的异步编程模型,考虑这样一个题目:读取一个文件,在控制台输出这个文件内容
varfs=require('fs'); fs.readFile('1.txt','utf8',function(err,data){ console.log(data); });
看起来很简单,再进一步:读取两个文件,在控制台输出这两个文件内容
varfs=require('fs'); fs.readFile('1.txt','utf8',function(err,data){ console.log(data); fs.readFile('2.txt','utf8',function(err,data){ console.log(data); }); });
要是读取更多的文件呢
varfs=require('fs'); fs.readFile('1.txt','utf8',function(err,data){ fs.readFile('2.txt','utf8',function(err,data){ fs.readFile('3.txt','utf8',function(err,data){ fs.readFile('4.txt','utf8',function(err,data){ //... }); }); }); });
这就是传说中的callbackhell,可以使用async来改善这段代码,但是在本例中我们要用promise/defer来改善它
promise基本概念
首先它是一个对象,它和javascript普通的对象没什么区别,同时,它也是一种规范,跟异步操作约定了统一的接口,表示一个异步操作的最终结果,以同步的方式来写代码,执行的操作是异步的,但又保证程序执行的顺序是同步的
1.promise只有三种状态,未完成,完成(fulfilled)和失败(rejected)
2.promise的状态可以由未完成转换成完成,或者未完成转换成失败
3.promise的状态转换只发生一次
promise有一个then方法,then方法可以接受3个函数作为参数。前两个函数对应promise的两种状态fulfilled,rejected的回调函数。第三个函数用于处理进度信息
为了理解它,一些重要原理必须记牢:.then()总是返回一个新的promise,如下面代码:
varpromise=readFile() varpromise2=promise.then(readAnotherFile,console.error)
这里then的参数readAnotherFile,console.error是代表异步操作成功后的动作onFulfilled或失败后的动作OnRejected,也就是说,读取文件成功后执行readAnotherFile函数,否则失败打印记录错误。这种实现是两个中只有一种可能
也可以理解为:
promiseSomething().then(function(fulfilled){ //当promise状态变成fulfilled时,调用此函数 },function(rejected){ //当promise状态变成rejected时,调用此函数 },function(progress){ //当返回进度信息时,调用此函数 });
Promise法则有两部分必须分离:
1.then()总是返回一个新的promise,每次你调用它,它不管回调做什么,因为.then()在回调被调用之前已经给了你一个承诺promise,回调的行为只影响承诺promise的实施,如果回调返回一个值,那么promise将使用那个值,如果这个值是一个promise,返回这个promise实施后的值给这个值,如果回调抛出错误,promise将拒绝错误
2.被.then()返回的promise是一个新的promise,它不同于那些.then()被调用的promise,promise长长的链条有时会好些隐藏这个事实,不管如何,每次.then()调用都会产生一个新的promise,这里必须注意的是你真正需要考虑的是你最后调用.then()可能代表失败,那么如果你不捕获这种失败,那么容易导致你的错误exception消失
来看一个利用q来处理这种问题的简单例子:
varQ=require('q'); vardefer=Q.defer(); /** *获取初始promise *@private */ functiongetInitialPromise(){ returndefer.promise; } /** *为promise设置三种状态的回调函数 */ getInitialPromise().then(function(success){ console.log(success); },function(error){ console.log(error); },function(progress){ console.log(progress); }); defer.notify('inprogress');//控制台打印inprogress defer.resolve('resolve');//控制台打印resolve defer.reject('reject');//没有输出。promise的状态只能改变一次
promise的传递
then方法会返回一个promise,在下面这个例子中,我们用outputPromise指向then返回的promise。
varoutputPromise=getInputPromise().then(function(fulfilled){ },function(rejected){ });
现在outputPromise就变成了受function(fulfilled)或者function(rejected)控制状态的promise了。直白的意思就是:当function(fulfilled)或者function(rejected)返回一个值,比如一个字符串,数组,对象等等,那么outputPromise的状态就会变成fulfilled。
在下面这个例子中,我们可以看到,当我们把inputPromise的状态通过defer.resovle()变成fulfilled时,控制台输出fulfilled.
当我们把inputPromise的状态通过defer.reject()变成rejected,控制台输出rejected
varQ=require('q'); vardefer=Q.defer(); /** *通过defer获得promise *@private */ functiongetInputPromise(){ returndefer.promise; } /** *当inputPromise状态由未完成变成fulfil时,调用function(fulfilled) *当inputPromise状态由未完成变成rejected时,调用function(rejected) *将then返回的promise赋给outputPromise *function(fulfilled)和function(rejected)通过返回字符串将outputPromise的状态由 *未完成改变为fulfilled *@private */ varoutputPromise=getInputPromise().then(function(fulfilled){ return'fulfilled'; },function(rejected){ return'rejected'; }); /** *当outputPromise状态由未完成变成fulfil时,调用function(fulfilled),控制台打印'fulfilled:fulfilled'。 *当outputPromise状态由未完成变成rejected,调用function(rejected),控制台打印'rejected:rejected'。 */ outputPromise.then(function(fulfilled){ console.log('fulfilled:'+fulfilled); },function(rejected){ console.log('rejected:'+rejected); }); /** *将inputPromise的状态由未完成变成rejected */ defer.reject();//输出fulfilled:rejected /** *将inputPromise的状态由未完成变成fulfilled */ //defer.resolve();//输出fulfilled:fulfilled
当function(fulfilled)或者function(rejected)抛出异常时,那么outputPromise的状态就会变成rejected
varQ=require('q'); varfs=require('fs'); vardefer=Q.defer(); /** *通过defer获得promise *@private */ functiongetInputPromise(){ returndefer.promise; } /** *当inputPromise状态由未完成变成fulfil时,调用function(fulfilled) *当inputPromise状态由未完成变成rejected时,调用function(rejected) *将then返回的promise赋给outputPromise *function(fulfilled)和function(rejected)通过抛出异常将outputPromise的状态由 *未完成改变为reject *@private */ varoutputPromise=getInputPromise().then(function(fulfilled){ thrownewError('fulfilled'); },function(rejected){ thrownewError('rejected'); }); /** *当outputPromise状态由未完成变成fulfil时,调用function(fulfilled)。 *当outputPromise状态由未完成变成rejected,调用function(rejected)。 */ outputPromise.then(function(fulfilled){ console.log('fulfilled:'+fulfilled); },function(rejected){ console.log('rejected:'+rejected); }); /** *将inputPromise的状态由未完成变成rejected */ defer.reject();//控制台打印rejected[Error:rejected] /** *将inputPromise的状态由未完成变成fulfilled */ //defer.resolve();//控制台打印rejected[Error:fulfilled]
当function(fulfilled)或者function(rejected)返回一个promise时,outputPromise就会成为这个新的promise.
这样做的意义在于聚合结果(Q.all),管理延时,异常恢复等等
比如说我们想要读取一个文件的内容,然后把这些内容打印出来。可能会写出这样的代码:
//错误的写法 varoutputPromise=getInputPromise().then(function(fulfilled){ fs.readFile('test.txt','utf8',function(err,data){ returndata; }); });
然而这样写是错误的,因为function(fulfilled)并没有返回任何值。需要下面的方式:
varQ=require('q'); varfs=require('fs'); vardefer=Q.defer(); /** *通过defer获得promise *@private */ functiongetInputPromise(){ returndefer.promise; } /** *当inputPromise状态由未完成变成fulfil时,调用function(fulfilled) *当inputPromise状态由未完成变成rejected时,调用function(rejected) *将then返回的promise赋给outputPromise *function(fulfilled)将新的promise赋给outputPromise *未完成改变为reject *@private */ varoutputPromise=getInputPromise().then(function(fulfilled){ varmyDefer=Q.defer(); fs.readFile('test.txt','utf8',function(err,data){ if(!err&&data){ myDefer.resolve(data); } }); returnmyDefer.promise; },function(rejected){ thrownewError('rejected'); }); /** *当outputPromise状态由未完成变成fulfil时,调用function(fulfilled),控制台打印test.txt文件内容。 * */ outputPromise.then(function(fulfilled){ console.log(fulfilled); },function(rejected){ console.log(rejected); }); /** *将inputPromise的状态由未完成变成rejected */ //defer.reject(); /** *将inputPromise的状态由未完成变成fulfilled */ defer.resolve();//控制台打印出test.txt的内容
方法传递
方法传递有些类似于Java中的try和catch。当一个异常没有响应的捕获时,这个异常会接着往下传递
方法传递的含义是当一个状态没有响应的回调函数,就会沿着then往下找
没有提供function(rejected)
varoutputPromise=getInputPromise().then(function(fulfilled){})
如果inputPromise的状态由未完成变成rejected,此时对rejected的处理会由outputPromise来完成
varQ=require('q'); varfs=require('fs'); vardefer=Q.defer(); /** *通过defer获得promise *@private */ functiongetInputPromise(){ returndefer.promise; } /** *当inputPromise状态由未完成变成fulfil时,调用function(fulfilled) *当inputPromise状态由未完成变成rejected时,这个rejected会传向outputPromise */ varoutputPromise=getInputPromise().then(function(fulfilled){ return'fulfilled' }); outputPromise.then(function(fulfilled){ console.log('fulfilled:'+fulfilled); },function(rejected){ console.log('rejected:'+rejected); }); /** *将inputPromise的状态由未完成变成rejected */ defer.reject('inputpromiserejected');//控制台打印rejected:inputpromiserejected /** *将inputPromise的状态由未完成变成fulfilled */ //defer.resolve();
没有提供function(fulfilled)
varoutputPromise=getInputPromise().then(null,function(rejected){})
如果inputPromise的状态由未完成变成fulfilled,此时对fulfil的处理会由outputPromise来完成
varQ=require('q'); varfs=require('fs'); vardefer=Q.defer(); /** *通过defer获得promise *@private */ functiongetInputPromise(){ returndefer.promise; } /** *当inputPromise状态由未完成变成fulfil时,传递给outputPromise *当inputPromise状态由未完成变成rejected时,调用function(rejected) *function(fulfilled)将新的promise赋给outputPromise *未完成改变为reject *@private */ varoutputPromise=getInputPromise().then(null,function(rejected){ return'rejected'; }); outputPromise.then(function(fulfilled){ console.log('fulfilled:'+fulfilled); },function(rejected){ console.log('rejected:'+rejected); }); /** *将inputPromise的状态由未完成变成rejected */ //defer.reject('inputpromiserejected'); /** *将inputPromise的状态由未完成变成fulfilled */ defer.resolve('inputpromisefulfilled');//控制台打印fulfilled:inputpromisefulfilled
可以使用fail(function(error))来专门针对错误处理,而不是使用then(null,function(error))
varoutputPromise=getInputPromise().fail(function(error){})
看这个例子:
varQ=require('q'); varfs=require('fs'); vardefer=Q.defer(); /** *通过defer获得promise *@private */ functiongetInputPromise(){ returndefer.promise; } /** *当inputPromise状态由未完成变成fulfil时,调用then(function(fulfilled)) *当inputPromise状态由未完成变成rejected时,调用fail(function(error)) *function(fulfilled)将新的promise赋给outputPromise *未完成改变为reject *@private */ varoutputPromise=getInputPromise().then(function(fulfilled){ returnfulfilled; }).fail(function(error){ console.log('fail:'+error); }); /** *将inputPromise的状态由未完成变成rejected */ defer.reject('inputpromiserejected');//控制台打印fail:inputpromiserejected /** *将inputPromise的状态由未完成变成fulfilled */ //defer.resolve('inputpromisefulfilled');
可以使用progress(function(progress))来专门针对进度信息进行处理,而不是使用then(function(success){},function(error){},function(progress){})
varQ=require('q'); vardefer=Q.defer(); /** *获取初始promise *@private */ functiongetInitialPromise(){ returndefer.promise; } /** *为promise设置progress信息处理函数 */ varoutputPromise=getInitialPromise().then(function(success){ }).progress(function(progress){ console.log(progress); }); defer.notify(1); defer.notify(2);//控制台打印1,2
promise链
promise链提供了一种让函数顺序执行的方法
函数顺序执行是很重要的一个功能。比如知道用户名,需要根据用户名从数据库中找到相应的用户,然后将用户信息传给下一个函数进行处理
varQ=require('q'); vardefer=Q.defer(); //一个模拟数据库 varusers=[{'name':'andrew','passwd':'password'}]; functiongetUsername(){ returndefer.promise; } functiongetUser(username){ varuser; users.forEach(function(element){ if(element.name===username){ user=element; } }); returnuser; } //promise链 getUsername().then(function(username){ returngetUser(username); }).then(function(user){ console.log(user); }); defer.resolve('andrew');
我们通过两个then达到让函数顺序执行的目的。
then的数量其实是没有限制的。当然,then的数量过多,要手动把他们链接起来是很麻烦的。比如
foo(initialVal).then(bar).then(baz).then(qux)
这时我们需要用代码来动态制造promise链
varfuncs=[foo,bar,baz,qux] varresult=Q(initialVal) funcs.forEach(function(func){ result=result.then(func) }) returnresult
当然,我们可以再简洁一点
varfuncs=[foo,bar,baz,qux] funcs.reduce(function(pre,current),Q(initialVal){ returnpre.then(current) })
看一个具体的例子
functionfoo(result){ console.log(result); returnresult+result; } //手动链接 Q('hello').then(foo).then(foo).then(foo); //控制台输出:hello //hellohello //hellohellohello //动态链接 varfuncs=[foo,foo,foo]; varresult=Q('hello'); funcs.forEach(function(func){ result=result.then(func); }); //精简后的动态链接 funcs.reduce(function(prev,current){ returnprev.then(current); },Q('hello'));
对于promise链,最重要的是需要理解为什么这个链能够顺序执行。如果能够理解这点,那么以后自己写promise链可以说是轻车熟路啊
promise组合
回到我们一开始读取文件内容的例子。如果现在让我们把它改写成promise链,是不是很简单呢?
varQ=require('q'), fs=require('fs'); functionprintFileContent(fileName){ returnfunction(){ vardefer=Q.defer(); fs.readFile(fileName,'utf8',function(err,data){ if(!err&&data){ console.log(data); defer.resolve(); } }) returndefer.promise; } } //手动链接 printFileContent('sample01.txt')() .then(printFileContent('sample02.txt')) .then(printFileContent('sample03.txt')) .then(printFileContent('sample04.txt'));//控制台顺序打印sample01到sample04的内容
很有成就感是不是。然而如果仔细分析,我们会发现为什么要他们顺序执行呢,如果他们能够并行执行不是更好吗?我们只需要在他们都执行完成之后,得到他们的执行结果就可以了
我们可以通过Q.all([promise1,promise2...])将多个promise组合成一个promise返回。注意:
1.当all里面所有的promise都fulfil时,Q.all返回的promise状态变成fulfil
2.当任意一个promise被reject时,Q.all返回的promise状态立即变成reject
我们来把上面读取文件内容的例子改成并行执行吧
varQ=require('q'); varfs=require('fs'); /** *读取文件内容 *@private */ functionprintFileContent(fileName){ //Todo:这段代码不够简洁。可以使用Q.denodeify来简化 vardefer=Q.defer(); fs.readFile(fileName,'utf8',function(err,data){ if(!err&&data){ console.log(data); defer.resolve(fileName+'success'); }else{ defer.reject(fileName+'fail'); } }) returndefer.promise; } Q.all([printFileContent('sample01.txt'),printFileContent('sample02.txt'),printFileContent('sample03.txt'),printFileContent('sample04.txt')]) .then(function(success){ console.log(success); });//控制台打印各个文件内容顺序不一定
现在知道Q.all会在任意一个promise进入reject状态后立即进入reject状态。如果我们需要等到所有的promise都发生状态后(有的fulfil,有的reject),再转换Q.all的状态,这时我们可以使用Q.allSettled
varQ=require('q'), fs=require('fs'); /** *读取文件内容 *@private */ functionprintFileContent(fileName){ //Todo:这段代码不够简洁。可以使用Q.denodeify来简化 vardefer=Q.defer(); fs.readFile(fileName,'utf8',function(err,data){ if(!err&&data){ console.log(data); defer.resolve(fileName+'success'); }else{ defer.reject(fileName+'fail'); } }) returndefer.promise; } Q.allSettled([printFileContent('nosuchfile.txt'),printFileContent('sample02.txt'),printFileContent('sample03.txt'),printFileContent('sample04.txt')]) .then(function(results){ results.forEach( function(result){ console.log(result.state); } ); });
结束promise链
通常,对于一个promise链,有两种结束的方式。第一种方式是返回最后一个promise
如returnfoo().then(bar);
第二种方式就是通过done来结束promise链
如foo().then(bar).done()
为什么需要通过done来结束一个promise链呢?如果在我们的链中有错误没有被处理,那么在一个正确结束的promise链中,这个没被处理的错误会通过异常抛出
varQ=require('q'); functiongetPromise(msg,timeout,opt){ vardefer=Q.defer(); setTimeout(function(){ console.log(msg); if(opt) defer.reject(msg); else defer.resolve(msg); },timeout); returndefer.promise; } /** *没有用done()结束的promise链 *由于getPromse('2',2000,'opt')返回rejected,getPromise('3',1000)就没有执行 *然后这个异常并没有任何提醒,是一个潜在的bug */ getPromise('1',3000) .then(function(){returngetPromise('2',2000,'opt')}) .then(function(){returngetPromise('3',1000)}); /** *用done()结束的promise链 *有异常抛出 */ getPromise('1',3000) .then(function(){returngetPromise('2',2000,'opt')}) .then(function(){returngetPromise('3',1000)}) .done();
附录:一个Promise简单的应用(Node.js笔记(5)promise)
附:Promises/A+规范
promise代表一个异步操作的最终结果。主要通过promise的then方法订阅其最终结果的处理回调函数,和订阅因某原因无法成功获取最终结果的处理回调函数。
更对详细见:Promises/A+
A与A+的不同点
- A+规范通过术语thenable来区分promise对象
- A+定义onFulfilled/onRejectd必须是作为函数来调用,而且调用过程必须是异步的
- A+严格定义了then方法链式调用时,onFulfilled/onRejectd的调用顺序
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。