以前就会经常用到 Promise,但是没有去探究内部的实现机制。 正好由这次在小程序中引入了 Promise ,探究下内部原理。
为什么使用 Promise
Promise 可以让我们避免回调的地狱。以前我们可能使用的是 bluebird 或者 Q,现在我们已经有了原生的实现。1
2
3
4
5
6
7
8
9
10
11func1(function (value1) {
func2(value1, function (value2) {
func3(value2, function (value3) {
func4(value3, function (value4) {
func5(value4, function (value5) {
// Do something with value 5
});
});
});
});
});
使用 Promise 后,可以把平行的代码变成竖状的易读的代码。1
2
3
4
5func1(value)
.then(func2)
.then(func3)
.then(func4)
.then(func5)
一个最常见的 Promise 的例子如下:1
2
3
4
5
6
7
8
9
10
11var request = require('request');
return new Promise((resolve, reject) => {
request.get(url, (error, response, body) => {
if(body) {
resolve(JSON.parse(body));
} else {
resolve({}); //reject(....);
}
})
})
小程序 Promise 化实现
小程序里,包括官方文档的 demo 里会看到很多的 cb, 比如我们获取用户身份信息就需要下面的操作:1
2
3
4
5
6
7
8
9
10wx.login({
success: function () {
wx.getUserInfo({
success: function (res) {
that.globalData.userInfo = res.userInfo;
typeof cb == "function" && cb(that.globalData.userInfo)
}
})
}
});
这样略微深的嵌套一定程度的阻碍了我们的阅读和理解,代码整体也不好查 bug 和 扩展。所以我考虑到用 Promise 扩展一层。
但是由于小程序已经去除了 自带的 Promise ,所以需要开发者自动引入 Promise 库,或者编写相应的 Promise 库。这里我引入 es6-promise 这个库。注意不要使用 bluebird ,bluebird 会导致android 上有报错,因为这个里面有用到一些小程序不支持的比如 document, window之类的对象。
选取一小段没使用的代码如下:1
2
3
4
5
6
7
8
9
10var Promise = require('es6-promise').Promise;
var wxLib = require('../wxLib);
wxLib.login()
.then(()=>{
wxLib.getUserInfo();
})
.catch(()=>{
wxLib.showErrorMsg();
})
promise 更易我们看清整个的结构。更好的控制异步流程, 也可以让我们使用本来不方便使用的 return, throw 等。
更多其他的用法
关于 Promise 的几个注意的地方
- Promise 对象是一个构造函数,所以才需要 new Promise 生成一个 Pormise 的实例对象。
- Promise 构造函数接受了两个参数,分别是 resolve 和 reject, 这两个函数由 js 引擎提供,不需要自己实现。
- resolve 是将 Promise 对象从 未完成 => 成功。(pending => resolved), reject 是 将对象的状态从 未完成=> 失败。(pending=>rejected)
- then 方法接收两个回调函数。第一个是 Promise 返回 resolved 的时候调用的, 第二个是 返回 rejected 的时候调用的。
- Promise catch 是 .then(null, rejection)的别名,里面的回调用于发生错误时使用。
1 | // 这种情况不会捕捉then 里的错误 |
关于Promise.all的用法
var p = Promise.all([p1, p2, p3]);
其中 Promise.all 会接收一个数组作为参数, p1,p2,p3都是 Promise 对象的实例。 如果不是,则调用 Promise.resolve 方法,将参数转化为 Promise 实例。
他们三个之间的关系是: 必须都变成 fulfilled , p才是 fulfilled, 只要有一个是 rejected, 就会是 rejected, 并且这个第一个被 reject 的实例的返回值就会给到 P。 haha, 挺团结的。
1 | let urls = [ |
有一个方法叫做 Promise.race() 跟 Promise.all 差不多。也是
var p = Promise.race([p1, p2, p3]);
p1, p2, p3 只要有一个率先改变,p 的状态就会改,并且把这个率先改的返回值给到 P。
Promise.resolve() 的 用法
上面提到了, promise.all 和 promise.race 的参数 p1, p2, p3 都必须是 promise 的实例对象。 如果不是,就需要转化为 Promise 对象。 Promise.resolve 就派上了用法。
1 | Promise.resolve('foo'); |
Promise.resolve 方法的参数分成下面几个情况:
Promise.resolve(value);
Promise.resolve(promise);
Promise.resolve(thenable);
1 | Promise.resolve("Success").then(function(value) { |
实现一个简易的 Promise
这里实现的过程我参考阅读了很多篇文章,感谢,具体有:
先搭一个简单的框架。
构造函数的实现
1 | // processor 就是传给 Promise 的函数 |
然后是对 resolve 和 reject 的实现, 我们用原生的 Promise 的时候不需要实现这两个函数,是因为 JS 引擎已经帮我们做了这件事情。
1 | this.resolve = (value) => { |
then 函数的实现
1 | Promise.prototype.then = (onResolved, onRejected) => { |
总结
上面只是一个粗浅的大概。如果要丰富 Promise , 还要去实现很多其他的内容,比如 catch 之类的。关于异步还有很多要学习,其实大部分都学习过,只是因为使用的少,没有考虑他内部的实现,也比较容易忘记。还是实践是王道。