理解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的使用方法介绍,希望对大家的学习有所帮助。