根据功能一步一步实现一个Promise
Promise的特性
先看下原生Promise的简单使用
1 | const p = new Promise((resolve, reject) => { |
要实现一个Promise我们首先需要了解它有哪些特性
- Promise是一个类, 在执行这个类的时候会传入一个执行器(handle), 这个执行器会立即执行.
- Promise会有三种状态
- Pending 等待
- Fulfilled 完成
- Rejected 失败
- 状态只能由
Pending --> Fulfilled或者Pending --> Rejected, 且一旦发生改变就不可二次修改. - Promise中使用resolve和reject两个函数来改变状态
- then方法内部做的就是判断状态
- 如果状态是成功, 调用成功回调函数
- 如果状态是失败, 调用失败回调函数
最简单的Promise
新建类
要实现上面的功能, 首先我们需要创建个类, 并且传入执行函数(handle)
handle函数会有两个参数分别是resolve和reject
1
2
3
4
5
6
7
8
9
10
11
12// 新建CPromise.js
// 新建CPromise类
class CPromise {
constructor(handle) {
// handle 是一个执行器, 进入会立即执行
handle(this.resolve, this.reject);
}
// 这边的resolve和reject是个箭头函数, this指向当前实例对象本身
resolve = () => {}
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// CPromise.js
// 定义三个常量表示状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class CPromise {
// 储存状态的变量, 赋初始值pending
status = PENDING
// 存放成功的值
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方法
接下来就是then函数了, then函数可以传入两个参数, 分别是失败和成功的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14// CPromise.js
class CPromise {
then(onFulfilled, onRejected) {
// 判断状态
if (this.status === FULFILLED) {
// 调用成功回调, 并将值返回
onFulfilled(this.value)
} else if (this.status === REJECTED) {
// 调用失败回调, 并将原因返回
onRejected(this.reason)
}
}
}将上面例子中的原生promise替换成CPromise, 执行代码
1
2
3
4
5
6
7
8
9
10
11
12const p = new CPromise((resolve, reject) => {
resolve("success");
reject("failure");
});
p.then(value => {
console.log('resolve ', value);
}, err => {
console.log('reject ', err);
})
// 输出: resolve success输出结果正确
在Promise类中加入异步逻辑
上面的例子还没有经过异步处理, 一旦加入异步逻辑, 代码执行就会异常
1 | const p = new CPromise((resolve, reject) => { |
分析原因: 主线程程序会立即执行, 而setTimeout是异步代码, 示例中的then函数会立即执行, resolve函数会在1s后才执行, 所以执行then函数时的status状态仍然是pending.then的参数函数不会执行
缓存成功与失败回调函数
1
2
3
4
5
6
7
8// CPromise.js
class CPromise {
// 存储成功回调函数
onFulfilledCallback = null
// 存储失败回调函数
onRejectedCallback = null
}
then函数中添加对pending状态的处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// CPromise.js
class CPromise{
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value)
} else if (this.status === REJECTED) {
onRejected(this.value)
} else if (this.status === PENDING) {
// 此时状态还未变化, 先讲回调函数缓存起来, 等状态变化时再执行
this.onFulfilledCallback = onFulfilled
this.onRejectedCallback = onRejected
}
}
}在resolve及reject中调用回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// CPromise.js
class CPromise{
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED
this.value = value
// 判断回调是否存在, 如果存在就调用
this.onFulfilledCallback && this.onFulfilledCallback(value)
}
}
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 判断回调是否存在, 如果存在就调用
this.onRejectedCallback && this.onRejectedCallback(reason);
}
}
}再执行上面的例子
1
2
3
4
5
6
7
8
9
10
11const p = new CPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000);
});
p.then(value => {
console.log('resolve ', value);
})
// 输出: resolve success输出结果正确
当then函数被多次调用
Promise的then函数是可以被多次调用的, 如果同时有三个then函数, promise被执行resolve时, 三个then都会被调用
改用数组来存储回调函数
1
2
3
4
5
6
7
8class CPromise{
// 存储成功回调函数
// onFulfilledCallback = null
onFulfilledCallbacks = [];
// 存储失败回调函数
// onRejectedCallback = null
onRejectedCallbacks = []
}将回调函数存放到数组中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class CPromise{
then(onFulfilled, onRejected) {
if (this.status === FULFILLED) {
onFulfilled(this.value);
} else if (this.status === REJECTED) {
onRejected(this.value);
} else if (this.status === PENDING) {
// 此时状态还未变化, 先讲回调函数缓存起来, 等状态变化时再执行
// this.onFulfilledCallback = onFulfilled;
this.onFulfilledCallbacks.push(onFulfilled)
// this.onRejectedCallback = onRejected;
this.onRejectedCallbacks.push(onRejected)
}
}
}循环调用成功和失败的回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class CPromise{
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
// 判断回调是否存在, 如果存在就调用
// this.onFulfilledCallback && this.onFulfilledCallback(value);
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(value)
}
}
};
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
// 判断回调是否存在, 如果存在就调用
// this.onRejectedCallback && this.onRejectedCallback(reason);
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason);
}
}
};
}执行代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const p = new CPromise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000);
});
p.then(value => {
console.log('resolve 1 ', value);
})
p.then((value) => {
console.log("resolve 2 ", value);
});
p.then((value) => {
console.log("resolve 3 ", value);
});
/**
* 输出:
* resolve 1 success
* resolve 2 success
* resolve 3 success
*/
实现then方法的链式调用
原生的promise是可以链式调用的, 并且then方法里面return一个返回值作为下一个then方法的参数, 如果return一个Promise对象, 就判断它的状态
而看我们现在的CPromise, 不具备链式调用功能.
1 | // test.js |
修改then方法, 返回CPromise. (为了方便描述, 下面我们将例子中链式调用的then函数分别称为then1和then2)
1 | class CPromise{ |
执行上面的例子, 结果符合预期
1 | resolve 1 success |
但是目前只支持同步方法的链式调用, 一旦加入异步逻辑还是会出错
1 | const p = new CPromise((resolve, reject) => { |
可以发现then2的onFulfilled未执行
分析原因: 异步时进入then1方法时状态为pending, onFulfilledCallbacks会缓存当前onFulfilled, 并在resolve方法被调用时再触发, 此时触发我们并没有对then1返回值进行处理.
解决方案: 将对返回值的处理封装成函数dealResolve, 异步时将dealResolve添加到onFulfilledCallbacks
1 | class CPromise{ |
再试试上面的例子
1 | const p = new CPromise((resolve, reject) => { |
结果符合预期
then方法中返回自己
如果then方法返回的是自己的Promise对象, 则会发生循环调用, 这时程序会报错
看看原生Promise会咋样
1 | const p = new Promise((resolve, reject) => { |
我们也在自己的CPromise中实现一下该功能
1 | class CPromise{ |
运行上面的例子
1 | const p = new CPromise((resolve, reject) => { |
执行后发现报错了, p1还未初始化完成不能使用. 我们需要创建一个异步函数去等待p2完成初始化.
考虑到promise本身是个微任务, 所以我们选取异步函数时也要选取一个微任务来完成我们的预期. 这里我们选用queueMicrotask.(最后会有宏任务与微任务的几种创建方式介绍)
添加微任务
1 | class CPromise { |
再次运行代码 符合预期
捕获错误及then链式调用其他状态处理
首先是执行器中的异常
1 | class CPromise{ |
输出例子
1 | const p = new CPromise((resolve, reject) => { |
then执行时的错误捕获
1 | class CPromise { |
输出例子
1 | const p = new CPromise((resolve, reject) => { |
补充对rejected的处理
1 | class CPromise { |
then中的参数变为可选
// todo
实现resolve与reject的静态调用
// todo
CPromise完整代码
1 | const PENDING = "pending"; |