在前面的文章说了要来手写promise,这里整理下实现思路。
概述
class内使用这些属性或者方法描述
promiseState保存当前状态pendingfulfilledrejectedpromiseResult保存执行结果promiseFulfillReactions保存pending之前注册的成功回调方法promiseRejectReactions保存pending之前注册的失败回调方法onFulfilledpromise then onFulfilled 方法onRejectedpromise 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
实现如下部分功能:
- 如果
onFulfilledonRejected不是函数,需要忽略掉 - 如果
onFulfilledonRejected是函数,在Promise实现后调用,以Promiseresolve的值或者reject的理由作为第一个参数,在Promise确定之前不可调用,并且只能最多调用一次 - 需要异步执行 then 中注册的回调(setTimeout、MutationObserver、process.nextTick)
onFulfilledonRejected必须作为函数调用,即没有thisthen方法必须返回一个promise2: Promise- 如果 onFulfilled、onRejected 返回一个值则
Promise2resolve(x), - 如果 onFulfilled、onRejected执行报错则
Promise2reject(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 中的一种,也就是微任务。也就是这里实际又多了一次微任务。