在前面的文章说了要来手写promise,这里整理下实现思路。
概述
class内使用这些属性或者方法描述
promiseState
保存当前状态pending
fulfilled
rejected
promiseResult
保存执行结果promiseFulfillReactions
保存pending之前注册的成功回调方法promiseRejectReactions
保存pending之前注册的失败回调方法onFulfilled
promise then onFulfilled 方法onRejected
promise then onRejected 方法
Promise A+ 规范参见这里:https://promisesaplus.com/#point-49
有些规范内提到的场景并未实现,比如对
thenable
的支持等,实现的部分包含如下所列出
异步执行回调
Promise的规范并没有要求一定要使用微任务,浏览器原生都是基于微任务这里也做类似实现,首先需要实现一个微任务管理器来管理微任务,像Vue nextTick
那样。
微任务有浏览器中的各类 Observer
(MutationObserver InterSectionObserver PerformanceObserver) 和 Promise
的回调,以及Node中的 process.nextTick
实现 nextTick 大致可以两种:
- 一种就是像 Vue
nextTick
会将当前事件循环中加入的所有fn
保存在一个队列,然后在一个微任务中执行清空一起洗澡,比如下图:
- 或者就是每次
nextTick
都加入一个新的微任务,然后通过浏览器Event Loop
来清空微任务时一个个依次洗澡,比如下图:
两种实现执行的结果是一样的,第一种方案在单次事件循环中调用只添加一次微任务,在接下来的代码中也使用第一种方法。
如下: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/* GLOBAL js */
const callbacks = [];
let pending = false;
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for (let i = 0; i < copies.length; i += 1) {
copies[i]();
}
}
let timerFunc;
function isNative(Ctor) {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString());
}
if (typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver)
// PhantomJS and iOS 7.x
|| MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// Use MutationObserver where native Promise is not available,
// e.g. PhantomJS, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1;
const observer = new MutationObserver(flushCallbacks);
const textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
characterData: true
});
timerFunc = () => {
counter = (counter + 1) % 2;
textNode.data = String(counter);
};
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Technically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks);
};
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0);
};
}
function nextTick(cb, onException) {
callbacks.push(() => {
try {
cb.call();
} catch (e) {
onException(e);
}
});
if (!pending) {
pending = true;
timerFunc();
}
}
window.nextTick = nextTick;
Promise 类
实现如下部分功能:
- 构造函数程序接受一个
function
参数,否则报错 function
参数最多接受两个参数(resolve
,reject
)function
执行改变promise状态,并异步
执行之前注册的回调方法- 构造函数执行出现异常则
promise
以异常作为reason
执行reject
- 执行
resolve(x)
如果x
为当前Promise实例,以TypeError
作为reason
执行reject
- 执行
resolve(x)
如果x
为Promise,则采用x
的状态
Promise.prototype.then
实现如下部分功能:
- 如果
onFulfilled
onRejected
不是函数,需要忽略掉 - 如果
onFulfilled
onRejected
是函数,在Promise
实现后调用,以Promise
resolve的值或者reject的理由作为第一个参数,在Promise
确定之前不可调用,并且只能最多调用一次 - 需要异步执行 then 中注册的回调(setTimeout、MutationObserver、process.nextTick)
onFulfilled
onRejected
必须作为函数调用,即没有this
then
方法必须返回一个promise2: Promise
- 如果 onFulfilled、onRejected 返回一个值则
Promise2
resolve(x)
, - 如果 onFulfilled、onRejected执行报错则
Promise2
reject(error)
- 如果 onFulfilled、onRejected 返回一个Promise则
Promise2
需要使用跟随该Promise的状态和结果
- 如果 onFulfilled、onRejected 返回一个值则
Promise.prototype.catch
实现如下部分功能:
- .then(null, rejection)或.then(undefined, rejection)的别名
Promise.resolve
实现如下部分功能:
- 返回一个以给定值
resolve
后的Promise 对象
Promise.reject
实现如下部分功能:
- 返回一个带有原因
reject
的Promise对象。
Promise.prototype.finally
实现如下部分功能:
- 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
Promise.all
Promise.any
Promise.race
Promise.allSettled
完整代码
1 | /* GLOBAL js */ |
测试
只实现了部分功能并不能通过官方测试,这里找了一个之前看到的态执行顺序的题目(看起来很厉害实际没啥用)来做测试
1 | function test1(Promise) { |
最后一个测试和原生Promise有点差异是因为,当 then方法中返回了
Promise
时,会产生一个 NewPromiseResolveThenableJob,这是属于 Promise Jobs 中的一种,也就是微任务。也就是这里实际又多了一次微任务。