探索Promise用法和机制

以前就会经常用到 Promise,但是没有去探究内部的实现机制。 正好由这次在小程序中引入了 Promise ,探究下内部原理。


为什么使用 Promise

Promise 可以让我们避免回调的地狱。以前我们可能使用的是 bluebird 或者 Q,现在我们已经有了原生的实现。

1
2
3
4
5
6
7
8
9
10
11
func1(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
5
func1(value)
.then(func2)
.then(func3)
.then(func4)
.then(func5)

一个最常见的 Promise 的例子如下:

1
2
3
4
5
6
7
8
9
10
11
var 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
10
wx.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
10
var Promise = require('es6-promise').Promise; 
var wxLib = require('../wxLib);

wxLib.login()
.then(()=>{
wxLib.getUserInfo();
})
.catch(()=>{
wxLib.showErrorMsg();
})

promise 更易我们看清整个的结构。更好的控制异步流程, 也可以让我们使用本来不方便使用的 return, throw 等。


更多其他的用法

关于 Promise 的几个注意的地方

  1. Promise 对象是一个构造函数,所以才需要 new Promise 生成一个 Pormise 的实例对象。
  2. Promise 构造函数接受了两个参数,分别是 resolve 和 reject, 这两个函数由 js 引擎提供,不需要自己实现。
  3. resolve 是将 Promise 对象从 未完成 => 成功。(pending => resolved), reject 是 将对象的状态从 未完成=> 失败。(pending=>rejected)
  4. then 方法接收两个回调函数。第一个是 Promise 返回 resolved 的时候调用的, 第二个是 返回 rejected 的时候调用的。
  5. Promise catch 是 .then(null, rejection)的别名,里面的回调用于发生错误时使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 这种情况不会捕捉then 里的错误
promise
.then(function(data) {
// success
}, function(err) {
// error
});

// better
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});

关于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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let urls = [
'/api/commits',
'/api/issues/opened',
'/api/issues/assigned',
'/api/issues/completed',
'/api/issues/comments',
'/api/pullrequests'
];

let promises = urls.map((url) => {
return new Promise((resolve, reject) => {
$.ajax({ url: url })
.done((data) => {
resolve(data);
})
})
})

Promise.all(promises)
.then((results) => {
// results is an array list
})

有一个方法叫做 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
2
Promise.resolve('foo');
new Promise(resolve => resolve('foo'));

Promise.resolve 方法的参数分成下面几个情况:

Promise.resolve(value);

Promise.resolve(promise);

Promise.resolve(thenable);

1
2
3
4
5
6
7
8
9
10
Promise.resolve("Success").then(function(value) {
console.log(value); // "Success"
}, function(value) {
// 不会被调用
});

var p = Promise.resolve([1,2,3]);
p.then(function(v) {
console.log(v[0]); // 1
});

实现一个简易的 Promise

这里实现的过程我参考阅读了很多篇文章,感谢,具体有:

  1. 剖析Promise内部结构
  2. how-do-promises-work
  3. JS Promise的实现原理

先搭一个简单的框架。


构造函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// processor 就是传给 Promise 的函数
function Promise(processor) {
this.status = 'pending';
this.data = undefined;
this.onResolvedCb = [];
this.onRejectedCb = [];

this.resolve = (value) => {
//TODO
}
this.reject = (reason) => {
//TODO
}

try{
(typeof processor === 'function') && processor(resolve, reject);
}catch(e) {
reject(e);
}

}

然后是对 resolve 和 reject 的实现, 我们用原生的 Promise 的时候不需要实现这两个函数,是因为 JS 引擎已经帮我们做了这件事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
this.resolve = (value) => {
if(this.status === 'resolved' || this.status === 'pending') {
this.status = 'resolved';
this.data = value;
for(var i = 0, l = this.onResolvedCb.length; i < l; i++) {
//执行回调函数
this.onResolvedCb[i].value;
}
}
}

// reject 就和 resolve 非常像
this.reject = (reson) => {
if(this.status === 'rejected' || this.status === 'pending'){
this.status = 'rejected';
//......
}
}

then 函数的实现

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
Promise.prototype.then = (onResolved, onRejected) => {
onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}


onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

if(this.status === 'resolved'){
return new Promise((resolve, reject) => {
try{
let x = onResolved(self.data);
if(x instanceof Promise) {
x.then(resolve, reject);
}
resolve(x);
} catch (e) {
reject(e);
}
})
}else if(this.status === 'rejected') {
return new Promise((resolve, reject) => {
//TODO
})
}else{
//pending
return new Promise((resolve, reject) => {
//TODO
})
}
}

总结

上面只是一个粗浅的大概。如果要丰富 Promise , 还要去实现很多其他的内容,比如 catch 之类的。关于异步还有很多要学习,其实大部分都学习过,只是因为使用的少,没有考虑他内部的实现,也比较容易忘记。还是实践是王道。