Promise then 原理分析
# 前言
先谈规范,再分析 polyfill 源码,最后实例解析。
# 规范
规范直接参考这边:【翻译】Promises/A+规范
返回值这部分重要且易错,这里单独提出来分析下
首先,then 方法必须返回一个 promise 对象
promise2 = promise1.then(onFulfilled, onRejected);
- 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:
[[Resolve]](promise2, x)
- 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
- 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
e.g.:
promise1 = Promise.resolve(1);onFulfilled = 2;
则promise2 = Promise {<resolved>: 1}
- 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
e.g.:
promise1 = Promise.reject(1);onRejected = 2;
则promise2 = Promise {<rejected>: 1}
- 不论 promise1 被 reject 还是被 resolve 时 promise2 都会被 resolve,只有出现异常时才会被 rejected。
e.g.:
promise1 = Promise.reject(1);onFulfilled =()=>1; onRejected=()=>2;
则promise2 = Promise {<resolved>: 2}
;e.g.:
promise1 = Promise.resolve(1);onFulfilled =()=>{throw new Error('test')}; onRejected=(e)=>console.log(e);
则promise2 = Promise {<rejected>: Error: test
# Promise 解决过程 [[Resolve]](promise, x)
promise 为 then 回调的返回值,运行 [[Resolve]](promise, x)
需遵循以下步骤:
- x 与 promise 相等
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
e.g.:test = {};test = Promise.resolve().then(()=>test)
//Promise {<rejected>: TypeError: Chaining cycle detected for promise #<Promise>}
- x 为 Promise
promise 接受x的状态
e.g.:promise2 = Promise.resolve().then(()=>Promise.reject(1))
//Promise {<rejected>: 1}
- x 为对象或函数
- 把 x.then 赋值给 then。用引用保持,防止其他地方又更改了then的值
- 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
- 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
- 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
Promise.resolve().then(()=>({a:1,then:function (res,rej){res(this.a)}}))
//Promise {<resolved>: 1}
- 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
Promise.resolve().then(()=>({a:1,then:function (res,rej){rej(this.a)}}))
//Promise {<rejected>: 1}
- 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
- 如果调用 then 方法抛出了异常 e:
- 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
- 否则以 e 为据因拒绝 promise
- x 不为对象或函数
以 x 为参数执行 promise
如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise
这个目前还不知道怎么写才能产生循环的 thenable 链 - -
# 原理
# then 操作解析
全局维护一个 microTask Queue ,每个 promise 自身维护一个 queue
当 promise 执行 then 方法时:
- 令 then 方法中的回调为 resolver,令 promise 的内部值为 outcome
- 创建一个 PENDING 状态的 Promise 对象 promise2
- 如果 promise 处于 PENDING 状态,将 “执行 resolver ,改变 promise2 状态” 作为队列项放入该 promise 对象自身维护的 queue
- 否则将 “执行 resolver ,改变 promise2 状态” 作为 microTask 放入 microTask Queue
- 返回 promise2
当同步代码执行完毕,开始执行 microTask Queue 中的任务
执行 microTask :
- 以 promise 的内部值 outcome 作为参数执行 resolver 并得到返回值 returnValue
- 若出现异常,将异常赋值给 returnValue
- 若未出现异常,对 returnValue 做些处理,这里不细讲
- 修改 promise2 的状态为 FULFILLED/REJECTED ,并修改 promise2 的内部值 outcome 为 returnValue
- 该 promise2 维护的 queue 中的队列项放入 microTask Queue
不断的执行 microTask 直到 microTask Queue 为空,一轮 event loop 结束
# 源码
对 lie.js (opens new window) (一个 Promise polyfill 库) 的代码做个精简,仅考虑普通的 then 回调和 resolve 方法
然后 microTask 任务执行接口我们直接用的 setTimeout ,不搞 Mutation 那些幺蛾子,代码如下
// immediate.js
var scheduleDrain = function () {
setTimeout(nextTick, 0);
};
var draining;
var queue = [];
// 在同步代码 task 执行之后,模拟清空 microTask queue
// 由于执行过程中可能有新的 microTask 所以用了双层循环
function nextTick() {
draining = true;
var i, oldQueue;
var len = queue.length;
while (len) {
oldQueue = queue;
queue = [];
i = -1;
while (++i < len) {
oldQueue[i]();
}
len = queue.length;
}
draining = false;
}
function immediate(task) {
if (queue.push(task) === 1 && !draining) {
scheduleDrain();
}
}
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
Promise 代码如下
// promise.js
// then 方法执行时作为参数 resolver 实例化 Promise
function INTERNAL () { }
const REJECTED = 'REJECTED';
const FULFILLED = 'FULFILLED';
const PENDING = 'PENDING';
function Promise (resolver) {
if (typeof resolver !== 'function') {
throw new TypeError('resolver must be a function');
}
this.state = PENDING;
this.queue = [];
this.outcome = void 0;
// 外部通过 new 实例化时执行
if (resolver !== INTERNAL) {
safelyResolveThenable(this, resolver);
}
}
Promise.prototype.then = function (onFulfilled, onRejected) {
// 特殊参数的处理
if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
typeof onRejected !== 'function' && this.state === REJECTED) {
return this;
}
// 创建一个 PENDING 状态的 promise
var promise = new this.constructor(INTERNAL);
// 根据原 promise 状态进行不同处理
if (this.state !== PENDING) {
var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
makeMicroTask(promise, resolver, this.outcome);
} else {
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
}
return promise;
};
Promise.resolve = function (value) {
if (value instanceof this) {
return value;
}
return handleResolve(new this(INTERNAL), value);
}
// promsie 内部队列项
function QueueItem (promise, onFulfilled, onRejected) {
this.promise = promise;
// 仅考虑 function类型的 onFulfilled
this.onFulfilled = onFulfilled;
this.callFulfilled = function(value) {
makeMicroTask(this.promise, this.onFulfilled, value);
};
// 暂不处理 onRejected
}
// 将 handleResolve 操作放入 microtask
function makeMicroTask (promise, func, value) {
immediate(function () {
handleResolve(promise, func(value));
});
}
// 改变 promise 状态,并通知 queue 中的 promise 执行 makeMicroTask
function handleResolve (self, value) {
self.state = FULFILLED;
self.outcome = value;
var i = -1;
var len = self.queue.length;
while (++i < len) {
self.queue[i].callFulfilled(value);
}
return self;
}
// 对 resolver 方法的两个参数(resolve,reject)进行包装
function safelyResolveThenable (self, thenable) {
var called = false;
function onError (value) {
if (called) {
return;
}
called = true;
// 暂不处理 reject
// handlers.reject(self, value);
}
function onSuccess (value) {
if (called) {
return;
}
called = true;
handleResolve(self, value);
}
thenable(onSuccess, onError);
}
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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# 实例分析
new Promise(resolve => {
console.log(1);
resolve(3);
Promise.resolve().then(()=> console.log(4))
}).then(num => {
console.log(num)
});
console.log(2)
2
3
4
5
6
7
8
答案
1243
Promise.resolve().then(() => {
console.log(1);
Promise.resolve().then(()=> console.log(4))
}).then(() => {
console.log(3)
});
console.log(2)
2
3
4
5
6
7
答案
2143
new Promise((r) => {
r()
console.log(1);
}).then(() => {
console.log(11)
}).then(() => {
console.log(12)
}).then(() => {
console.log(13)
})
var promise = new Promise((r) => {
r()
console.log(2);
let promise = Promise.resolve().then(()=> console.log(21)).then(()=> console.log(22)).then(()=> console.log(23))
promise.then(()=>console.log(29))
Promise.resolve().then(()=> console.log(24)).then(()=> console.log(25)).then(()=> console.log(26))
})
promise.then(() => {
console.log(27)
})
promise.then(() => {
console.log(28)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
答案
【1,2,11,21,24,27,28,12,22,25,13,23,26,29】
# 如何测试
当我们的 polyfill 写完后,需要测试下是否符合规范,我们自己写测试用例肯定是不可能的,这里有一个测试库 promises-tests (opens new window)
const tests = require("promises-aplus-tests");
const Promise = require("./index");
const deferred = function() {
let resolve, reject;
const promise = new Promise(function(_resolve, _reject) {
resolve = _resolve;
reject = _reject;
});
return {
promise: promise,
resolve: resolve,
reject: reject
};
};
const adapter = {
deferred
};
tests.mocha(adapter);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
这样就可以做测试了