Promise

从$.Deferred()说起

最早对Promise的理解是从网上了解到的,是jQuery内部的近似的实现,文章在这里:jQuery的deferred对象详解.
其实说的就是deferred对象。虽然说平时很少刻意去实现deferred,但是却尝尝在$.ajax后面使用到deferred。虽然详细的可以直接去看上面那篇文章,毕竟写的具体,不过这里还是要简单扯一扯,避免单(yi)调(wang).

现在控制台跑一下看看$.Deferred对象内部方法:

$.Deferred

deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。

这里copy一下他人的API翻译,和上文的图上方法比还是有缺失,不过暂时不打算补充(一来懒癌发作,二来这些就够将Deferred用起来了,到了不够用完全可以google之):

1
2
3
4
5
6
7
8
9
$.Deferred() 生成一个deferred对象
deferred.done() 指定操作成功时的回调函数
deferred.fail() 指定操作失败时的回调函数
deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。
deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。
deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。
$.when() 为多个操作指定回调函数
deferred.then(): 把done和fail结合写在一起了,传递两个回调函数即可。
deferred.always(): 这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。

用起Deferred实际上掌握上面几个方法就OK了。
这里修改一下参考文章的代码来举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var wait = function(dtd){
var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
setTimeout(function(){
var randomNum = Math.random();
console.log(randomNum);
if(randomNum>0.5){
dtd.resolve();
}else{
dtd.reject();
}
},3000);
return dtd.promise(); // 返回promise对象
};
$.when(wait())
.done(function(){ alert("哈哈,成功了!")})
.fail(function(){ alert("出错啦!")});

就这一段代码就够展示怎么用了。done和fail就是一个钩子函数,关联到状态上,通过resolve和reject改变Deferred对象的状态,一旦状态改变就触发特定状态下的钩子上注册的函数。
如果不能理解没关系,复制到控制台,跑两遍就明白怎么回事了。

不过这玩意儿不是很好用,自己封装一下就OK了(就是下面的initDeferred函数),这样就可以直接传入函数。就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function initDeferred(callback){
return $.when((function(){
var dtd = $.Deferred();
callback(dtd);
return dtd.promise();
})())
}
// 如何使用:
// 1.定义一个函数时,内部使用状态修改
function test(dtd){
setTimeout(function(){
var randomNum = Math.random();
console.log(randomNum);
if(randomNum>0.5){
dtd.resolve();
}else{
dtd.reject();
}
}
// 2.调用它
var d1 = initDeferred(test)
d1.done(function(){ alert("哈哈,成功了!")})
.fail(function(){ alert("出错啦!")});

这样就方便很多了,第一个函数设置Deferred别名,然后记住resolve和reject方法即可,非常简单!

Promise

Promise是JavaScript异步操作解决方案。随着ES6标准的发布,它已经成为一个标准产物。
通过Promise,我们可以避免金字塔般的深度回调嵌套。它的工作原理和$.Deferred()其实是很类似的。

这里用Promise实现一下上面$.Deferred的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var promise = new Promise(function(resolve, reject) {
var randomNum = Math.random();
setTimeout(function(){
if(randomNum>0.5){
console.log("success",randomNum)
resolve();
} else {
console.log("fail",randomNum)
reject();
}
},3000)
});

promise.then(function(value,error) {
alert("success")
}, function(error) {
alert("fail")
});

在$.Deferred对象里面有fail可以捕获错误,而Promise里面使用catch();它的用法和fail几乎一样,所以不再举例。
这里展示下Promise的原型:

除了提到的then和catch,还有个chain,看名称应该是链式操作用的。暂时还没用过。

除了原型上的方法,Promise对象还有自己私有的方法。
下面方法描述来自MDN:

Promise.all(iterable)
返回一个promise对象,当iterable参数里所有的promise都被完成后,该promise也会被完成。
Promise.race(iterable)
当iterable参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。
Promise.reject(reason)
调用Promise的rejected句柄,并返回这个Promise对象。
Promise.resolve(value)
用成功值value完成一个Promise对象。如果该value为可继续的(thenable,即带有then方法),返回的Promise对象会“跟随”这个value,采用这个value的最终状态;否则的话返回值会用这个value满足(fullfil)返回的Promise对象。

Promise.all:

1
2
3
4
5
6
7
8
9
var a1 = new Promise(function(resolve,reject){
// ...
})
var a2 = new Promise(function(resolve,reject){
// ...
})
Promise.all([a1,a2],function(value){
console.log("都执行完毕了")
})

Promise.race:race和all有些不同,如果说all像是数组的every,那么Promise.race就像是数组的some.恩,这里抄一块代码看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
return new Promise(function (resolve) {
setTimeout(function () {
//console.log(delay)
resolve(delay);
}, delay);
});
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
timerPromisefy(1),
timerPromisefy(32),
timerPromisefy(64),
timerPromisefy(128)
]).then(function (value) {
console.log(value); // => 1
});

可以把代码贴到浏览器.race仅仅会在第一次resolve之后触发回调。但是需要注意的是timerPromisefy却一共执行了4次,去掉console.log的注释就可以看到了。

API就是这几个,跑顺了几个DEMO之后似乎也没有什么值得说的了。想想还是深入一下尝试自己想办法简单实现这个ES6中Promise。这里仅仅实现resolve和inject好了,当然,为了可以用还有then也需要实现。

暂结

本来打算谢谢node下的buldbird的,然而发现最终还是没能写下去。看来在node上还是要多练练。本篇就暂时到这里吧。等node实践多一些再回来补充。

— THE END —

参考资料

《jQuery的deferred对象详解》
《Deferred对象》
《ECMAScript 6 入门》
《JavaScript Promise迷你书(中文版》