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
