Promise实现原理
# Promise实现原理
我们都知道 Js 是单线程的,但是一些高耗时操作就带来了进程阻塞问题。为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)。
在异步模式下,创建异步任务主要分为宏任务与微任务两种。ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。
原文链接:从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 (opens new window)
# Promise 核心逻辑实现
原生🌰:
const promise = new Promise((resolve, reject) => {
resolve('success')
reject('err')
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
// 输出 resolve success
2
3
4
5
6
7
8
9
10
11
12
# 基本原理
Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行
Promise 会有三种状态
- Pending 等待
- Fulfilled 完成
- Rejected 失败
状态只能由 Pending --> Fulfilled 或者 Pending --> Rejected,且一但发生改变便不可二次修改;
Promise 中使用 resolve 和 reject 两个函数来更改状态;
then 方法内部做的事情就是状态判断
- 如果状态是成功,调用成功回调函数
- 如果状态是失败,调用失败回调函数
直接上代码:
// MyPromise.js
// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
// 新建 MyPromise 类
export default class MyPromise { // 使用export default向外暴露MyPromise类
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
executor(this.resolve, this.reject)
}
// 储存状态的变量,初始值是 pending
status = PENDING;
// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
}
}
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
}
}
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
}
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
# 我的理解
左图为promise 逻辑代码,右图为实例应用代码
通过executor传入resolve和reject方法,随后在实例方法中调用resolve方法。此时MyPromise.js
中的resolve方法会执行,修改状态并保存传入的值。
// MyPromise.js
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
}
}
2
3
4
5
6
7
8
9
10
11
随后会执行then方法,它会判断当前状态是成功还是失败,并返回值。随后在实例方法中接收并输出。
// MyPromise.js
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
}
}
2
3
4
5
6
7
8
9
10
11
onFulfilled
和onRejected
保存的是实例中的代码如:value{console.log(...)}
// 实例
promise.then(value => {
console.log('resolve', value) // 输出resolve success
}, reason => {
console.log('reject', reason)
})
2
3
4
5
6
# 在 Promise 中加入异步逻辑
先看🌰
// test.js
const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
// 没有打印信息!!!
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这是因为:
主线程代码立即执行,setTimeout 是异步代码,then 会马上执行,这个时候判断 Promise 状态,状态是 Pending,然而之前并没有判断等待这个状态
对代码进行改造:
# 缓存成功与失败回调
// MyPromise.js
// 在yPromise 类中新增
// 存储成功回调函数
onFulfilledCallback = null;
// 存储失败回调函数
onRejectedCallback = null;
2
3
4
5
6
7
# then 方法中的 Pending 的处理
// MyPromise.js
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
} else if (this.status === PENDING) {
// ==== 新增 ====
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallback = onFulfilled;
this.onRejectedCallback = onRejected;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# resolve 与 reject 中调用回调函数
// MyPromise.js
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// ==== 新增 ====
// 判断成功回调是否存在,如果存在就调用
this.onFulfilledCallback && this.onFulfilledCallback(value);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// MyPromise.js
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// ==== 新增 ====
// 判断失败回调是否存在,如果存在就调用
this.onRejectedCallback && this.onRejectedCallback(reason)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
最后再执行一下🌰:
// test.js
const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
promise.then(value => {
console.log('resolve', value)
}, reason => {
console.log('reject', reason)
})
// 等待 2s后 输出 resolve success
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 我的理解
在promise执行异步任务时,主线程代码会立即执行,而setTimeout
是异步代码,所以会先执行.then中的代码,这时候MyPromise.js
中的then会判断当前状态,如果是PENDING则将.then中的成功和失败回调保存并在异步任务执行完后,调用resolve或reject时,返回保存的成功或失败回调。
# 实现 then 方法多次调用添加多个处理函数
先看一个🌰:
// test.js
const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 2000);
})
promise.then(value => {
console.log(1)
console.log('resolve', value)
})
promise.then(value => {
console.log(2)
console.log('resolve', value)
})
promise.then(value => {
console.log(3)
console.log('resolve', value)
})
// 3
// resolve success
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
目前的代码只打印出了第三个then的调用,Promise的then方法是可以被多次调用的。这里有三个then的调用,如果是同步回调,那么直接返回当前值;如果是异步回调,那么保存的成功或失败的回调需要使用不同的变量保存。
# MyPromise 类中新增两个数组
// MyPromise.js
// 存储成功回调函数
// onFulfilledCallback = null;
onFulfilledCallbacks = [];
// 存储失败回调函数
// onRejectedCallback = null;
onRejectedCallbacks = [];
2
3
4
5
6
7
8
# 回调函数存入数组中
// MyPromise.js
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调,并且把值返回
onFulfilled(this.value);
} else if (this.status === REJECTED) {
// 调用失败回调,并且把原因返回
onRejected(this.reason);
} else if (this.status === PENDING) {
// ==== 新增 ====
// 因为不知道后面状态的变化,这里先将成功回调和失败回调存储起来
// 等待后续调用
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 循环调用 成功和失败回调
// MyPromise.js
// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// ==== 新增 ====
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// MyPromise.js
// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// ==== 新增 ====
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
再来执行一下🌰:
// 完美打印所有then的调用
1
resolve success
2
resolve success
3
resolve success
2
3
4
5
6
7
# 实现then方法的链式调用
then 方法要链式调用那么就需要返回一个 Promise 对象 then 方法里面 return 一个返回值作为下一个 then 方法的参数,如果是 return 一个 Promise 对象,那么就需要判断它的状态
举个🌰:
// test.js
const MyPromise = require('./MyPromise')
const promise = new MyPromise((resolve, reject) => {
// 目前这里只处理同步的问题
resolve('success')
})
function other () {
return new MyPromise((resolve, reject) =>{
resolve('other')
})
}
promise.then(value => {
console.log(1)
console.log('resolve', value)
return other()
}).then(value => {
console.log(2)
console.log('resolve', value)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
用目前的手写代码会报错,无法链式调用
修改一下代码:
// MyPromise.js
class MyPromise {
......
then(onFulfilled, onRejected) {
// ==== 新增 ====
// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const promise2 = new MyPromise((resolve, reject) => {
// 这里的内容在执行器中,会立即执行
if (this.status === FULFILLED) {
// 获取成功回调函数的执行结果
const x = onFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(x, resolve, reject);
} else if (this.status === REJECTED) {
onRejected(this.reason);
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(onFulfilled);
this.onRejectedCallbacks.push(onRejected);
}
})
return promise2;
}
}
function resolvePromise(x, resolve, reject) {
// 判断x是不是 MyPromise 实例对象
if(x instanceof MyPromise) {
// 执行 x,调用 then 方法,目的是将其状态变为 fulfilled 或者 rejected
// x.then(value => resolve(value), reason => reject(reason))
// 简化之后
x.then(resolve, reject)
} else{
// 普通值
resolve(x)
}
}
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
执行后的结果:
1
resolve success
2
resolve other
2
3
4