Promise 是 ES6中中收录的异步操作封装, 通常在回调/ 事件/ 消息等异步操作中有显著的优势, 让我们在更方便的操作异步也让代码更加清晰.包括 ES7中的 Async/Await 也是对异步操作的封装, 不过 Async 更像是 Generator 的语法糖.
基础实现
学习剖析 Promise 之基础篇用一个最常见的应用来剖析 Promise
, 通过异步获取用户 Id, 然后作一些处理. 平常我们最常用的是回调的方式来处理, 下面用 Promise
的方式来处理.1
2
3
4
5
6
7
8
9
10
11const getUserId = _ => {
new Promise((resolve, reject) => {
axios.get('http://example.com/api', params: param).then(res => {
resolve(JSON.parse(res).id)
})
})
}
getUserId().then(id => {
console.log(id)
// do something...
})
getUserId 函数返回一个 Promise
, 在他的 then
方法中放入异步操作成功之后的回调函数. 这种方式明显比我们之前常用的回调函数更加方便而且易读, 也更加容易避免 callback hell.
那么满足这样的场景的Promise
是怎样实现的呢, 下面我们可以简单的实现一下最基础的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
29
30
31
32
33
34
35
36
37function Promise (fn) {
let value = null
let deferreds = []
// 4. 存入异步成功需要的回调函数
// 此时指向两个回调函数 id => {}
this.then = function (onFulfilled) {
deferreds.push(onFulfilled)
return this
}
// 3. 执行 deferreds 队列中的回调函数
// 此时 value 为123
function resolve (value) {
deferreds.map(deferred => {
deferred(value)
})
}
// 1. 执行创建 Promise 实例时传入的函数
// 并传入 resolve 供在适当时触发回调
fn(resolve)
}
const getUserId = _ => {
return new Promise(resolve => {
// 2. 执行 resolve
resolve(123)
})
}
getUserId().then( id => {
console.log('this is callback!')
console.log(id) // 123
}).then(id => {
console.log('this is second callback!')
console.log(id) // 123
})
- 调用
then
方法将回调函数存入 deferreds 队列. - 创建
Promise
实例时传入函数和resolve
,resolve
用于在适当的时间触发回调函数. - 真正执行回调函数的是
deferreds
队列中的元素. resolve
函数接受一个参数, 用于回调函数使用, 即异步操作的返回结果.
可能大家已经发现, 以上代码并不能真正执行到回调函数.根据上面标注的序号就是代码的执行顺序, 这是因为现在还是同步函数, Promise
函数中的 resolve
函数会先于 this.then
函数执行,此时 deferreds
队列中还是空的, 以至于后面的回调函数也无法执行. 所以我们要保证回调以异步的方式执行, 以保证执行顺序. 可以通过 setTimeout
将 resolve
中的回调函数放在执行栈的末尾.1
2
3
4
5
6
7
8function resolve (value) {
// 将执行的回调的逻辑放入执行栈末尾
setTimeout(function () {
deferreds.map(deferred => {
deferred(value)
})
}, 0)
}
现在就可以看到, then 中的回调函数能够正常执行了.
引入状态
现在我们引入规范 Promises/A+
中所说的 States, 它有三个互斥的状态: pending/ fulfilled/ rejected.
现在我们来改进下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25function Promise (fn) {
let value = null
let deferreds = []
let state = 'pending'
this.then = function (onFulfilled) {
if (state === 'pending') {
deferreds.push(onFulfilled)
return this
}
onFulfilled(value)
return this
}
function resolve (newValue) {
value = newValue
state = 'fulfilled'
setTimeout(_ => {
deferreds.map(deferred => {
deferred(value)
})
}, 0)
}
fn(resolve)
}
串行 Promise
串行 Promise
是指当 promise 达到 fuifilled 状态之后, 再进行下一个 promise. 比如上例中的我们拿到 userId 之后还需要用这个 userId 去获取用户的名称/ 住址/ 手机号等其他信息.
使用的伪代码类似这样:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20getUserId()
.then(getUserInfoById)
.then(userInfo => {
// do something
})
const getUserInfoById = id => {
return new Promise(resolve => {
axios.get('http://example.com/api', params: {
id: 123
}).then(response => {
resolve(JSON.parse(response).info)
})
})
}
const getUserId = _ => {
return new Promise(resolve => {
resolve(123)
})
}
串行的困难在于如何将前后的 Promise
衔接起来, 首先对 then
方法改造:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18this.then = function (onFulfilled) {
// bridge promise
return new Promise(resolve => {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
})
})
}
let handle = deferred => {
if (state === 'pending') {
deferreds.push(deferred)
return
}
let ret = deferred.onFulfilled(value)
deferred.resolve(ret)
}
then
方法中返回一个新创建的 Promise 实例作为返回值, 这是串行的基础, 由于返回类型一样所以依然支持链式.then
方法中的形参onFulfilled
和新创建的 Promise 实例中的resolve
均放入当前 promise 的 deferreds 队列中.handle
方法作为当前 promise 的内部方法, 较之前的then
方法只增加了一行deferred.resolve(ret)
.
在当前 promise 的异步成功之后执行 handle
方法时, 先执行 onFulfilled
方法, 然后将其返回值作为 resolve
方法的实参传入.
再改造 resolve
方法, 把代码整理一下: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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55function Promise (fn) {
let value = null
let deferreds = []
let state = 'pending'
this.then = function (onFulfilled) {
// 创建一个新的 Promise作为返回值
// 将当前 promise 的回调函数和新创建的 resolve
// 放入 deferreds 队列
return new Promise (resolve => {
handle({
onFulfilled: onFulfilled || null
resolve: resolve
})
})
}
const handle = deferred => {
// 初始化状态时, 往 deferreds 队列添加
if (state === 'pending') {
deferreds.push(deferred)
return
}
// 当前 promise 达到 'fulfilled' 状态之后
// 先执行回调函数, 再将回调函数的返回值(ret)
// 传递给 resolve 函数
let ret = deferred.onFulfilled(value)
deferred.resolve(ret)
}
const resolve = newValue => {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
let then = newValue.then
if (typeof then === 'function') {
then.call(newValue, resolve)
return
}
}
value = newValue
state = 'fulfilled'
setTimeout(_ => {
deferreds.map(deferred => {
handle(deferred)
})
}, 0)
}
// 执行 promise 实例中的回调函数
fn(resolve)
}
getUserId()
.then(getUserInfoById)
.then(userInfo => {
// do something
console.log('this is second callback')
})
现在 resolve
支持传入一个 promise 实例的参数了, 执行顺序如下:
getUserId
生成的 promise1, 进入 Promise 函数执行fn(resolve)
由于getUserId
的内部是一个异步操作, 下一步会直接执行this.then(onFulfilled)
.this.then()
返回一个新的 promise2 函数( 即: bridge promise)作为链式调用, promise2 重新实例化再执行fn(resolve)
, 则进入handle({...})
此时 handle 的参数为 getUserInfoById 和 resolve2, 接着被 push 到deferreds1
队列中; 再接着执行下一个this.then()
生成 promise3,deferreds2
队列中 push 进第二个 then 的匿名函数(userInfo => {…}) 和 resolve3.- 执行
resolve(123)
, 进入resolve(newValue)
执行handle(deferred)
此时 deferred 为 getUserInfoById 和 resolve2, 执行 handle 内部的deferred.onFulfilled(value)
也就是 getUserInfoById 方法从而生成 promise4, 再到deferred.resolve(ret)
这个时候 ret 就为 promise4, 传入resolve(newValue)
执行then.call(promise4, resolve2)
. - 接着上一步进入
this.then()
生成 promise5(bridge promise), deferred4 压入 resolve2 和 resolve5; 在执行 getUserInfoById 中的resolve({name: 'cara'...})
, 进入 setTimeout 中的handle(deferred)
, 到 handle 函数内部deferred.onFulfilled(value)
其实执行的是resolve2({...})
resolve2中的deferred
保存的是的uerInfo => {}
匿名函数和resolve3
;deferred.resolve
其实执行的是resolve5
, 由于 resolve3 和 resolve5 中的deferred
都是空的于是完成整个流程.
接下来再加入错误处理和异常判断: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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70function Promise(fn) {
var state = 'pending',
value = null,
deferreds = [];
this.then = function (onFulfilled, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
};
function handle(deferred) {
if (state === 'pending') {
deferreds.push(deferred);
return;
}
var cb = state === 'fulfilled'
? deferred.onFulfilled
: deferred.onRejected,
ret;
if (cb === null) {
cb = state === 'fulfilled'
? deferred.resolve
: deferred.reject;
cb(value);
return;
}
try {
ret = cb(value);
deferred.resolve(ret);
} catch (e) {
deferred.reject(e);
}
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve, reject);
return;
}
}
state = 'fulfilled';
value = newValue;
finale();
}
function reject(reason) {
state = 'rejected';
value = reason;
finale();
}
function finale() {
setTimeout(function () {
deferreds.forEach(function (deferred) {
handle(deferred);
});
}, 0);
}
fn(resolve, reject);
}
之前的文字描述不好理解, 所以还是画了一个执行流程图
没有画 reject
的情况, 因为reject
跟 resolve
的流程是一样的, 两个一起画显得更乱就单独把 resolve
拎出来. 在加入 handle
和 resolve
在 promise 函数中作为内部方法后实在不易理解. 主要就是通过闭包来保存 promise 对象的变量引用, 将回调函数和 resolve 函数保存在缓存队列中, 在通过 resolve 完成链式调用.
其他实现
看完之前的那篇文章实在觉得有点难理解, 于是又找了其他的实现方式JS Promise的实现原理比之前那篇更好理解一些, 所以还是记录一下好了.
这篇文章作者说到的
Promise
重点:
Promise
是一个承诺, 所以不管成功与否都要有一个执行结果, 因此 Promise 构造函数有一个函数类型的参数resolver
来作为与该 promise 对象关联的任务.- 三种状态不可逆转.
resolver
函数封装了需要执行的异步操作, 内部:resolve
和reject
两个参数; 分别代表执行成功和执行失败需要执行的操作.then
提供成功或失败的响应处理, 于是有了onResolve
和onReject
.then
方法返回一个新的 promise(bridge promise), 提供链式操作及串行, 如果直接返回 this 那么就是并行显然不符合我们的需求. 前一个 promise 需要知道下一个 promise 对象是谁及其任务引用, 而后一个 promise 要提供一个给前一个 promise 成功或失败时需要执行的任务, 因此添加一个闭包makeCallback
调用将 promise 及其关联任务传递进去, 返回一个新函数, 前一个 promise 对象就将持有返回函数的引用, 调用返回函数时就能访问到 promise 对象和关联任务.resolve
和reject
函数在异步成功或失败的时候调用, 并传递成功的数据和失败的原因.run
函数执行异步相关的回调函数.
代码结构
- 构造函数
1 | function Promise (resolver) { |
then
方法
1 | this.prototype.then = function (onResolve, onReject) { |
makeCallback
函数
1 | // promise 对象/ 回调函数(关联任务)/ 类型 |
resolve
函数和reject
函数
这两个函数都需要一个参数来接收结果, 由于状态只能转换一次所以两个函数都需要判断状态.
1 | // 成功 |
run
函数
用来执行异步相关的回调函数.
1 | function run (promise) { |
run
函数中的 callbacks 就是 makeCallback
所返回的函数
- 完善
makeCallback
函数
1 | function makeCallback (promise, callback, action) { |
总结
我一开始是先看的剖析 Promise 之基础篇, 前半部分确实很亲民, 但是到加入 hanlde
和改造 resolve
函数 就开始懵了, 然后懵懵懂懂的去看JS Promise 的原理实现, 不过在最复杂的 makeCallback
函数中解释有点一笔带过的意思. 不过看了这篇帮助我理解之前的剖析 Promise 之基础篇, 就返回去看基础篇画了一遍流程图才算看懂, 里面的闭包用的太精妙了.
Created on 2017-12-26 by Cara