理解JavaScript中Promise的使用
Javascript采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(PyramidofDoom),绝对是一种糟糕的编程体验。于是便有了CommonJS的Promises/A规范,用于解决回调金字塔问题。本文先介绍Promises相关规范,然后再通过解读一个迷你的Promises以加深理解。
什么是Promise
一个Promise对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。它允许你以一种同步的方式编写异步代码。例如,如果你想要使用PromiseAPI异步调用一个远程的服务器,你需要创建一个代表数据将会在未来由Web服务返回的Promise对象。唯一的问题是目前数据还不可用。当请求完成并从服务器返回时数据将变为可用数据。在此期间,Promise对象将扮演一个真实数据的代理角色。接下来,你可以在Promise对象上绑定一个回调函数,一旦真实数据变得可用这个回调函数将会被调用。
Promise对象曾经以多种形式存在于许多语言中。
去除厄运的回调金字塔(PyramidofDoom)
Javascript中最常见的反模式做法是回调内部再嵌套回调。
//回调金字塔 asyncOperation(function(data){ //处理`data` anotherAsync(function(data2){ //处理`data2` yetAnotherAsync(function(){ //完成 }); }); });
引入Promises之后的代码
promiseSomething() .then(function(data){ //处理`data` returnanotherAsync(); }) .then(function(data2){ //处理`data2` returnyetAnotherAsync(); }) .then(function(){ //完成 });
Promises将嵌套的callback,改造成一系列的.then的连缀调用,去除了层层缩进的糟糕代码风格。Promises不是一种解决具体问题的算法,而已一种更好的代码组织模式。接受新的组织模式同时,也逐渐以全新的视角来理解异步调用。
各个语言平台都有相应的Promise实现
- Java'sjava.util.concurrent.Future
- Python'sTwisteddeferredsandPEP-3148futures
- F#'sAsync
- .Net'sTask
- C++11'sstd::future
- Dart'sFuture
- Javascript'sPromises/A/B/D/A+
下面我来相信了解一下javascript语言环境下各个规范的一些细节。
Promises/A规范
promise表示一个最终值,该值由一个操作完成时返回。
- promise有三种状态:**未完成**(unfulfilled),**完成**(fulfilled)和**失败**(failed)。
- promise的状态只能由**未完成**转换成完成,或者**未完成**转换成**失败**。
- promise的状态转换只发生一次。
promise有一个then方法,then方法可以接受3个函数作为参数。前两个函数对应promise的两种状态fulfilled和rejected的回调函数。第三个函数用于处理进度信息(对进度回调的支持是可选的)。
promiseSomething().then(function(fulfilled){ //当promise状态变成fulfilled时,调用此函数 },function(rejected){ //当promise状态变成rejected时,调用此函数 },function(progress){ //当返回进度信息时,调用此函数 });
如果promise支持如下连个附加方法,称之为可交互的promise
- get(propertyName)
获得当前promise最终值上的一个属性,返回值是一个新的promise。
- call(functionName,arg1,arg2,...)
调用当然promise最终值上的一个方法,返回值也是一个新的promise。
Promises/B规范
在Promises/A的基础上,Promises/B定义了一组promise模块需要实现的API
when(value,callback,errback_opt)
如果value不是一个promise,那么下一事件循环callback会被调用,value作为callback的传入值。如果value是一个promise,promise的状态已经完成或者变成完成时,那么下一事件循环callback会被调用,resolve的值会被传入callback;promise的状态已经失败或者变成失败时,那么下一事件循环errback会被调用,reason会作为失败的理由传入errback。
asap(value,callback,errback_opt)
与when最大的区别,如果value不是一个promise,会被立即执行,不会等到下一事件循环。
enqueue(taskFunction)
尽可能快地在接下来的事件循环调用task方法。
get(object,name)
返回一个获得对象属性的promise。
post(object,name,args)
返回一个调用对象方法的promise。
put(object,name,value)
返回一个修改对象属性的promise。
del(object,name)
返回一个删除对象属性的promise。
makePromise(descriptorObject,fallbackFunction)
返回一个promise对象,该对象必须是一个可调用的函数,也可能是可被实例化的构造函数。
- 第一个参数接受一个描述对象,该对象结构如下,
{"when":function(errback){...},"get":function(name){...},"put":function(name,value){...},"post":function(name,args){...},"del":function(name){...},}
上面每一个注册的handle都返回一个resolvedvalue或者promise。
- 第二个参数接受一个fallback(message,...args)函数,当没有promise对象没有找到对应的handle时该函数会被触发,返回一个resolvedvalue或者promise。
defer()
返回一个对象,该对象包含一个resolve(value)方法和一个promise属性。
当resolve(value)方法被第一次调用时,promise属性的状态变成完成,所有之前或之后观察该promise的promise的状态都被转变成完成。value参数如果不是一个promise,会被包装成一个promise的ref。resolve方法会忽略之后的所有调用。
reject(reasonString)
返回一个被标记为失败的promise。
一个失败的promise上被调用when(message)方法时,会采用如下两种方法之一
1.如果存在errback,errback会以reason作为参数被调用。when方法会将errback的返回值返回。
2.如果不存在errback,when方法返回一个新的reject状态的promise对象,以同一reason作为参数。
ref(value)
如果value是promise对象,返回value本身。否则,返回一个resolved的promise,携带如下handle。
1.when(errback),忽略errback,返回resolved值
2.get(name),返回resolved值的对应属性。
3.put(name,value),设置resolved值的对应属性。
4.del(name),删除resolved值的对应属性。
5.post(name,args),调用resolved值的对应方法。
6.其他所有的调用都返回一个reject,并携带"PromisedoesnothandleNAME"的理由。
isPromise(value)Boolean
判断一个对象是否是promise
method(nameString)
获得一个返回name对应方法的promise。返回值是"get","put","del"和"post"对应的方法,但是会在下一事件循环返回。
Promises/D规范
为了增加不同promise实现之间的可互操作性,Promises/D规范对promise对象和Promises/B规范做了进一步的约定。以达到鸭子类型的效果(Duck-typePromise)。
简单来说Promises/D规范,做了两件事情,
1、如何判断一个对象是Promise类型。
2、对Promises/B规范进行细节补充。
甄别一个Promise对象
Promise对象必须是实现promiseSend方法。
1.在promise库上下文中,如果对象包含promiseSend方法就可以甄别为promise对象
2.promiseSend方法必须接受一个操作名称,作为第一个参数
3.操作名称是一个可扩展的集合,下面是一些保留名称
1.when,此时第三个参数必须是rejection回调。
1.rejection回调必须接受一个rejection原因(可以是任何值)作为第一个参数
2.get,此时第三个参数为属性名(字符串类型)
3.put,此时第三个参数为属性名(字符串类型),第四个参数为新属性值。
4.del,此时第三个参数为属性名
5.post,此时第三个参数为方法的属性名,接下来的变参为方法的调用参数
6.isDef
4.promiseSend方法的第二个参数为resolver方法
5.promiseSend方法可能接受变参
6.promiseSend方法必须返回undefined
对Promises/B规范的补充
Promises/D规范中对Promises/B规范中定义的ref、reject、def、defer方法做了进一步细致的约束,此处略去这些细节。
Promises/A+规范
前面提到的Promises/A/B/D规范都是有CommonJS组织提出的,Promises/A+是有一个自称为Promises/A+组织发布的,该规范是以Promises/A作为基础进行补充和修订,旨在提高promise实现之间的可互操作性。
Promises/A+对.then方法进行细致的补充,定义了细致的PromiseResolutionProcedure流程,并且将.then方法作为promise的对象甄别方法。
此外,Promises/A+还提供了兼容性测试工具,以确定各个实现的兼容性。
实现一个迷你版本的Promise
上面扯了这么多规范,现在我们看看如何实现一个简单而短小的Promise。
1、状态机
varPENDING=0; varFULFILLED=1; varREJECTED=2; functionPromise(){ //storestatewhichcanbePENDING,FULFILLEDorREJECTED varstate=PENDING; //storevalueorerroronceFULFILLEDorREJECTED varvalue=null; //storesucess&failurehandlersattachedbycalling.thenor.done varhandlers=[]; }
2、状态变迁
仅支持两种状态变迁,fulfill和reject
//... functionPromise(){ //... functionfulfill(result){ state=FULFILLED; value=result; } functionreject(error){ state=REJECTED; value=error; } }
fulfill和reject方法较为底层,通常更高级的resolve方法开放给外部。
//... functionPromise(){ //... functionresolve(result){ try{ varthen=getThen(result); if(then){ doResolve(then.bind(result),resolve,reject) return } fulfill(result); }catch(e){ reject(e); } } }
resolve方法可以接受一个普通值或者另一个promise作为参数,如果接受一个promise作为参数,等待其完成。promise不允许被另一个promisefulfill,所以需要开放resolve方法。resolve方法依赖一些帮助方法定义如下:
/** *CheckifavalueisaPromiseand,ifitis, *returnthe`then`methodofthatpromise. * *@param{Promise|Any}value *@return{Function|Null} */ functiongetThen(value){ vart=typeofvalue; if(value&&(t==='object'||t==='function')){ varthen=value.then; if(typeofthen==='function'){ returnthen; } } returnnull; } /** *Takeapotentiallymisbehavingresolverfunctionandmakesure *onFulfilledandonRejectedareonlycalledonce. * *Makesnoguaranteesaboutasynchrony. * *@param{Function}fnAresolverfunctionthatmaynotbetrusted *@param{Function}onFulfilled *@param{Function}onRejected */ functiondoResolve(fn,onFulfilled,onRejected){ vardone=false; try{ fn(function(value){ if(done)return done=true onFulfilled(value) },function(reason){ if(done)return done=true onRejected(reason) }) }catch(ex){ if(done)return done=true onRejected(ex) } }
这里resolve和doResolve之间的递归很巧妙,用来处理promise的层层嵌套(promise的value是一个promise)。
构造器
//... functionPromise(fn){ //... doResolve(fn,resolve,reject); }
.done方法
//... functionPromise(fn){ //... functionhandle(handler){ if(state===PENDING){ handlers.push(handler); }else{ if(state===FULFILLED&& typeofhandler.onFulfilled==='function'){ handler.onFulfilled(value); } if(state===REJECTED&& typeofhandler.onRejected==='function'){ handler.onRejected(value); } } } this.done=function(onFulfilled,onRejected){ //ensurewearealwaysasynchronous setTimeout(function(){ handle({ onFulfilled:onFulfilled, onRejected:onRejected }); },0); } //... }
.then方法
//... functionPromise(fn){ //... this.then=function(onFulfilled,onRejected){ varself=this; returnnewPromise(function(resolve,reject){ returnself.done(function(result){ if(typeofonFulfilled==='function'){ try{ returnresolve(onFulfilled(result)); }catch(ex){ returnreject(ex); } }else{ returnresolve(result); } },function(error){ if(typeofonRejected==='function'){ try{ returnresolve(onRejected(error)); }catch(ex){ returnreject(ex); } }else{ returnreject(error); } }); }); } //... }
$.promise
jQuery1.8之前的版本,jQuery的then方法只是一种可以同时调用done、fail和progress这三种回调的速写方法,而Promises/A规范的then在行为上更像是jQuery的pipe。jQuery1.8修正了这个问题,使then成为pipe的同义词。不过,由于向后兼容的问题,jQuery的Promise再如何对Promises/A示好也不太会招人待见。
此外,在Promises/A规范中,由then方法生成的Promise对象是已执行还是已拒绝,取决于由then方法调用的那个回调是返回值还是抛出错误。在JQuery的Promise对象的回调中抛出错误是个糟糕的主意,因为错误不会被捕获。
小结
最后一个例子揭示了,实现Promise的关键是实现好doResolve方法,在完事以后触发回调。而为了保证异步setTimeout(fun,0);是关键一步。
Promise一直用得蛮顺手的,其很好的优化了NodeJS异步处理时的代码结构。但是对于其工作原理却有些懵懂和好奇,于是花了些精力查阅并翻译了Promise的规范,以充分的理解Promise的细节。
以上就是关于JavaScript中Promise的使用方法介绍,希望对大家的学习有所帮助。