手写Promise

前面的文章说了要来手写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 保存在一个队列,然后在一个微任务中执行清空一起洗澡,比如下图:

vue-nextTick

  • 或者就是每次 nextTick 都加入一个新的微任务,然后通过浏览器 Event Loop 来清空微任务时一个个依次洗澡,比如下图:

vue-nextTick

两种实现执行的结果是一样的,第一种方案在单次事件循环中调用只添加一次微任务,在接下来的代码中也使用第一种方法。

如下:

nextTick.js
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, rejectfunction 执行改变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的状态和结果

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
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/* GLOBAL js */

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
// 默认状态
promiseState = PENDING;

// resolved 执行队列
promiseFulfillReactions = [];

// rejected 执行队列
promiseRejectReactions = [];

// 执行结果
promiseResult = '';

static name = 'MyPromise';

static resolve(value) {
return new MyPromise((resolve) => {
resolve(value);
});
}

static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}

asyncRunTasks(tasks, callback) {
nextTick(() => {
const copies = [].concat(tasks);
for (let i = 0; i < copies.length; i += 1) {
const handle = copies[i];
if (typeof handle === 'function') {
handle.call(undefined, this.promiseResult);
}
}
if (callback) callback();
});
}

constructor(executor) {
if (typeof executor !== 'function') throw new Error('参数必须为一个function');

const $resolve = (result) => {
// 只能变更一次
if (this.promiseState !== PENDING) return undefined;

// #2.3.1
// result 等于当前实例 需要报错
if (result === this) throw new Error('TypeError');

// 2.3.2
// result 一个promise
if (result instanceof MyPromise) {
return result.then($resolve, $reject);
}

// 2.3.3 未兼容

this.promiseResult = result;
this.promiseState = FULFILLED;

// 异步执行已注册的回调
this.asyncRunTasks(this.promiseFulfillReactions, () => {
// 同时清空成功和失败队列
this.promiseFulfillReactions.length = 0;
this.promiseRejectReactions.length = 0;
});

return undefined;
};

const $reject = (reason) => {
// 只能变更一次
if (this.promiseState !== PENDING) return;

this.promiseResult = reason instanceof Error ? reason : new Error(reason);
this.promiseState = REJECTED;

// 异步执行已注册的回调
this.asyncRunTasks(this.promiseRejectReactions, () => {
// 同时清空成功和失败队列
this.promiseFulfillReactions.length = 0;
this.promiseRejectReactions.length = 0;
});
};

try {
executor($resolve, $reject);
} catch (e) {
$reject(e);
}
}

then(onFulfilled, onRejected) {
// .then() onFulfilled/onRejected 不是function需要忽略
if (typeof onFulfilled !== 'function') onFulfilled = result => result;
if (typeof onRejected !== 'function') {
onRejected = (reason) => {
throw reason;
};
}

// then 方法返回一个 Promise
return new MyPromise((innerResolve, innerReject) => {

// 通过try catch运行回调方法,捕获到异常执行reject
// 执行结果如果是Promise,内部的Promise的状态由返回的Promise状态决定
const genCallBackFn = fn => ((result) => {
try {
const innerResult = fn(result);

if (innerResult instanceof MyPromise) {
// 返回Promise
innerResult.then(innerResolve, innerReject);
} else {
innerResolve(innerResult);
}
} catch (e) {
console.log(e);
innerReject(e);
}
});

if (this.promiseState === PENDING) {
// pending
this.promiseFulfillReactions.push(genCallBackFn(onFulfilled));
this.promiseRejectReactions.push(genCallBackFn(onRejected));
} else if (this.promiseState === FULFILLED) {
// fulfilled
// 微任务异步执行 onFulfilled 回调
this.asyncRunTasks(genCallBackFn(onFulfilled));
} else {
// rejected
// 微任务异步执行 onRejected 回调
this.asyncRunTasks(genCallBackFn(onRejected));
}
});
}

catch(onRejected) {
return this.then(undefined, onRejected);
}
}

window.MyPromise = MyPromise;

window.myPromise = new MyPromise((resolve, reject) => {
window.resolve = resolve;
window.reject = reject;
});

测试

只实现了部分功能并不能通过官方测试,这里找了一个之前看到的态执行顺序的题目(看起来很厉害实际没啥用)来做测试

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
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
102
103
104
105
106
107
function test1(Promise) {
Promise.resolve()
.then(() => {
console.log('then1');
Promise.resolve().then(() => {
console.log('then1-1');
});
})
.then(() => {
console.log('then2');
});
}

function test2(Promise) {
const p = Promise.resolve();

p.then(() => {
console.log('then1');
Promise.resolve().then(() => {
console.log('then1-1');
});
}).then(() => {
console.log('then1-2');
});

p.then(() => {
console.log('then2');
});
}

function test3(Promise) {
const p = Promise.resolve().then(() => {
console.log('then1');
Promise.resolve().then(() => {
console.log('then1-1');
});
}).then(() => {
console.log('then2');
});

p.then(() => {
console.log('then3');
});
}

function test4(Promise) {
Promise.resolve()
.then(() => {
console.log('then1');
Promise.resolve()
.then(() => {
console.log('then1-1');
return 1;
})
.then(() => {
console.log('then1-2');
});
})
.then(() => {
console.log('then2');
})
.then(() => {
console.log('then3');
})
.then(() => {
console.log('then4');
});
}

function test5(Promise) {
Promise.resolve()
.then(() => {
console.log('then1');
Promise.resolve()
.then(() => {
console.log('then1-1');
return Promise.resolve();
})
.then(() => {
console.log('then1-2');
});
})
.then(() => {
console.log('then2');
})
.then(() => {
console.log('then3');
})
.then(() => {
console.log('then4');
});
}


[test1, test2, test3, test4, test5].forEach((testItem, index) => {
// 每个test 间隔 1s执行
setTimeout(() => {
console.log(`\n\n---- 测试function: ${testItem.name} ---`);

console.log('原生Promise:');
testItem.call(null, Promise);
setTimeout(() => {
console.log('\nMyPromise:');
testItem.call(null, MyPromise);
}, 10);
}, 1000 * index);
});

最后一个测试和原生Promise有点差异是因为,当 then方法中返回了 Promise 时,会产生一个 NewPromiseResolveThenableJob,这是属于 Promise Jobs 中的一种,也就是微任务。也就是这里实际又多了一次微任务。

测试例子参考来源:https://juejin.im/post/6869573288478113799