最早接触Promise的时候是在jquery,当时看的懵懵懂懂,没有去深入研究,只处于知道怎么用的阶段。现在在写微信小程序的时候发现不能使用ES6中的Promises,才发现自己知识的薄弱,查查资料,深度全面的了解下Promise。

简介

Promise:用来传递异步操作的对象。作为一个异步编程的一种解决方案而生。Promise的中文是承诺,对未来发生的许诺。成功会怎样,失败了又会返回什么。

其实在很早,Promise就实现了。我接触它的时候是jquery(1.5)中$.Deferred.Promise()创建promise对象,JQuery的ajax返回的就是一个deferred对象(deferred 和Promise很容易搞混)。

为什么选择

因为使用简单,提供统一的接口来操作异步。代码可读性强,使用类似同步操作的方式操作异步过程,避免了地狱般回调函数的层层嵌套。莫急 后面代码有详细例子。

ES6 Promise

而ES6只是将其写进语言标准,统一了用法,并原生的提供了Promise对象。

三种状态

Pending(进行中)、Resolved(已完成)、Rejected(已失败)

Api & 实战

  • Promise.resolve() // 成功返回
  • Promise.reject() // 失败返回
  • Promise.prototype.then()
  • Promise.prototype.catch()
  • Promise.all() // 所有的完成 var p = Promise.all([p1,p2,p3]);
  • Promise.race() // 竞速,完成一个即可

传统callback 回调思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
地狱般嵌套:
asyncFunc1(function(){
//...
asyncFunc2(function(){
//...
asyncFunc3(function(){
//...
asyncFunc4(function(){
//...
asyncFunc5(function(){
//... ...
});
});
});
});
});

或者你觉得可以稍微优雅点的地狱

1
2
3
4
5
6
7
8
9
10
11
12
asyncFunc1(){
setTimeout(asyncFunc2, 2000);
}
asyncFunc2(){
setTimeout(asyncFunc3, 2000);
}
asyncFunc3(){
setTimeout(asyncFunc3, 2000);
}
asyncFunc4(){
console.log('asyncFunc4 complete!');
}

而引用promise的链式处理异步后,代码可读性会强,逻辑会更清晰:

1
2
3
4
5
6
7
8
9
10
function asyncFunc1(){
return new Promise(function (resolve, reject) { //...
})
}
asyncFunc1()
.then(asyncFunc2)
.then(asyncFunc3)
.then(asyncFunc4)
.then(asyncFunc5);

典型错误(我曾经也犯过这样的错误):

1
2
3
4
5
6
7
8
9
10
11
12
13
new Promise(function(res, rej) {
console.log(Date.now() + " start setTimeout 1");
setTimeout(res, 2000);
}).then(function() {
console.log(Date.now() + " timeout 1 call back");
new Promise(function(res, rej) {
console.log(Date.now() + " start setTimeout 2");
setTimeout(res, 3000);
}).then(function() {
console.log(Date.now() + " timeout 2 call back");
})
});
这样子就陷入无限嵌套的噩梦之中,就不是我们要的promise了。

then函数的onFulfilled(onCompleted)回调函数会返回一个新的Promise变量,你可以再次调用这个新的Promise变量的then函数

但是问题来了,如果多个异步嵌套中有个方法返回错误(reject)了,promise将会如何处理 将要如何处理。看代码:

1
2
3
4
5
6
7
8
var promise3 = new Promise((resolve, reject) => {
reject("this is promise3 reject catch");
}).then((msg) => {
console.log('1' + msg);
}).catch((err) => {
console.log('2' + err);
});
//console: 2this is promise3 reject catch

当如果多异步嵌套中有一个请求返回错误,会直接往外抛,在catch中被拦截,而多嵌套异步请求将停止。

隐式包装Promise:在第一个 then中使用 return来返回一个 string, 而不是用 resolve或者 reject,效果其实是一样的。当然不只是 string 也可以直接 return Object等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var promise = new Promise((resolve, reject) => {
var promise = new Promise((resolve, reject) => {
console.log("promise starts");
setTimeout(() => {
resolve("this is promise resolve");
}, 2000);
});
resolve(promise);
}).then((msg) => {
console.log('msg: ' + msg);
return "promise .then()隐式包装resolved Promise";
}, (err) => {
console.log(err);
}).then((word) => {
console.log(word);
});
------ console.log -------
promise starts
msg: this is promise resolve
promise .then()隐式包装resolved Promise

显示包装Promise:在then中返回一个Promise对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var promise = new Promise((resolve, reject) => {
console.log("promise starts1");
var promise = new Promise((resolve, reject) => {
console.log("promise starts2");
setTimeout(() => {
resolve("this is promise resolve");
}, 2000);
});
console.log("promise starts3");
resolve(promise);
}).then((msg) => {
console.log("promise starts4");
console.log('msg: ' + msg);
return Promise.resolve("promise .then()显式包装resolved Promise");
}, (err) => {
console.log(err);
}).then((word) => {
console.log(word);
});
---- console log ------
promise starts1
promise starts2
promise starts3
undefined
promise starts4
msg: this is promise resolve
promise .then()显式包装resolved Promise

Promise.all的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var promise6 = new Promise((resolve, reject) => {
var promiseArr = [];
for (var i = 0; i < 5; ++i) {
promiseArr.push(new Promise((resolve, reject) => {
console.log(`promise6_${i}starts`);
((index) => {
setTimeout(() => {
console.log(`before promise6_${index} resolved`);
resolve(`this is promise6_${index} resolve`);
}, index * 1000);
})(i);
}));
}
resolve(Promise.all(promiseArr));
}).then((msgArr) => {
console.log(`promise6 all resolved ${msgArr}`);
});
//运行结果
//promise6_0 starts
//promise6_1 starts
//promise6_2 starts
//promise6_3 starts
//promise6_4 starts
//before promise6_0 resolved
//before promise6_1 resolved
//before promise6_2 resolved
//before promise6_3 resolved
//before promise6_4 resolved
//promise6 all resolved this is promise6_0 resolve,this is promise6_1 resolve,this is promise6_2 resolve,this is promise6_3 resolve,this is promise6_4 resolve

异常处理的相关内容比较少 参考自http://www.codesec.net/view/493391.html

兼容性

东西是很好用 但是兼容性太差。比如微信小程序就无法使用。也是因此让重新学习Promise的动机.兼容性如下图:

兼容新

解决方案(小程序)

用polyFill的解决。也就是自己引用第三方库,类似Q、When.js、 bluebird(26.7kb) 等。而且听说第三方库的效率会更高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var Promise = require('../libs/bluebird.min') //我用了bluebird.js
function wxPromisify(fn) {
return function (obj = {}) {
return new Promise((resolve, reject) => {
obj.success = function (res) {
resolve(res)
}
obj.fail = function (res) {
reject(res)
}
fn(obj)
})
}
}
module.exports = {
wxPromisify: wxPromisify
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var util = require('../utils/util')
var getLocationPromisified = util.wxPromisify(wx.getLocation)
getLocationPromisified({
type: 'wgs84'
}).then(function (res) {
var latitude = res.latitude
var longitude = res.longitude
var speed = res.speed
var accuracy = res.accuracy
}).catch(function () {
console.error("get location failed")
})

小程序兼容性参考自https://segmentfault.com/a/1190000007392283

Promiseの缺点

  1. 无法取消。一旦新建就会执行,没方法取消
  2. 当处于Pending(进行中)状态的时候,无法得知最近进展到哪个阶段