小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
一、前言
最近在一些技术群聊天的时候发现,很多人对手写Promise、Promise.all、发布订阅模式之类的比较抵触。部分人会为了面试去特地背,然后过了一段时间又忘记了。因为这些api在工作中都十分常用,本文就根据实际的用法,来倒推出promise的实现。
二、用法解析
最基础的用法
可以看到下面的代码共分为几部分
- 执行
new Promise
生成实例的时候传入的executor函数 - executor函数中又带了
resolve
和reject
这两个函数作为入参,他们两个都是在Promise内部根据不同的状态来实现的 .then
中也需要传一个函数,来作为Promise执行成功后的回调.then
中的res就是我们在resolve()
时传的入参.then
还可以链式调用
三、实现
以下的各版本的代码,大家可以配合测试用例执行一下,然后对比一下差异。
1. 最简单的实现(第一版)
对,你没有看错!!就是短短的10几行代码而已。能理解这个,后面的其他的实现继续倒推即可
代码和注释
let _onResolve = null // 这是第一次成功会执行的函数,也就是.then中的 res => {}
class MyPromise {
// 在new的时候会执行构造函数的逻辑
constructor(executor) {
const resolve = (params) => {
// 当Promise中调用resolve这个函数时,其实就相当于开始执行.then中的逻辑
_onResolve(params)
}
const reject = (params) => {}
// 这里执行的是传进来的(resolve, reject) => {}这个函数
executor(resolve, reject)
}
then(onResolve, onReject) {
_onResolve = onResolve
}
}
测试用例
// 测试用例
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
const a = 1
if (a > 0) {
console.log(`执行Promise中异步的逻辑`, 1) // 1.输出1
resolve(2)
} else {
reject('异常信息处理')
}
}, 500)
})
myPromise.then((res) => {
console.log(`第一次执行then`, res) // 2.输出2
})
缺少的功能
- 缺少链式调用
- 没有对reject的状态进行处理
如何解决
- 首先是链式调用的核心解析
- 本质上是在执行完
.then
之后,返回之前new Promise
生成的实例,也就是代码中的myPromise
,所以我们让.then
函数的返回值为myPromise
这个实例,即return this
,就能一直调用 - 链式调用中存在多个
.then
,所以要在resolve之后依次执行这些函数。这时候我们不能再用上面代码中的_onResolve
来当做要执行的.then
逻辑了。而是要新增一个数组callbacks
把所有.then
的函数放进去,等到resolve
的时候遍历这个数组,依次执行里面的函数- 这种多个函数延迟调用的情形在其他地方也会用到,一般都是用一个数组来存储,比如
发布订阅模式
、Promise.all
- 这种多个函数延迟调用的情形在其他地方也会用到,一般都是用一个数组来存储,比如
- 本质上是在执行完
好,了解完这两个核心的概念之后,我们就直接来一版带链式调用的。
2. 带链式调用的(第二版)
代码和注释
/**
* 带链式调用版本的实现
* 在第一版的基础上修改
* @author waldon
* @date 2021-09-30
*/
// let _onResolve = null // 因为有多个函数执行所以这个注释掉
const callbacks = [] // 存储.then的多个函数
class MyPromise {
// 在new的时候会执行构造函数的逻辑
constructor(executor) {
const handle = (params) => {
let _params = params // 这里如果是对象,应该深拷贝,避免影响入参
// 遍历执行.then中的函数
for (const onResolve of callbacks) {
// 第二次.then中的入参就是上次return的值,所以这里的params直接等于.then函数的执行结果
_params = onResolve && onResolve(_params)
}
}
const resolve = (params) => {
handle(params)
}
const reject = (params) => {}
executor(resolve, reject)
}
// 每次执行.then的时候相当于把函数放进数组中,延迟到resolve的时候执行
then(onResolve, onReject) {
callbacks.push(onResolve)
return this
}
}
测试用例
// 测试用例
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
const a = 1
if (a > 0) {
console.log(`执行Promise中异步的逻辑`, 1) // 1.输出1
resolve(2)
} else {
reject('异常信息处理')
}
}, 500)
})
myPromise.then((res) => {
console.log(`第一次执行then`, res) // 2.输出2
return 3
}).then((res) => {
console.log(`第二次执行then`, res) // 3.输出3
})
缺少的功能
- 没有对reject的状态进行处理
如何解决
我们先不理.catch
这个语法糖,先来一版最基本的。reject状态最核心的部分,就是需要一个增加一个变量来区分Promise是执行了resolve还是reject。
.then(onResolve, onReject)
中存在两个回调函数,我们就定义这个变量的初始状态state
为pending
,resolve的状态为fulfilled
,reject的状态为rejected
。这里用业界规范的命名状态,主要是为了便于理解而已。- 所以在Promise中基本实现应该为
then(onResolve, onReject) {
callbacks.push({
onResolve,
onReject
})
return this
}
- 后面在resolve或reject中执行
.then
的逻辑的时候,会根据state
这个状态来决定执行onResolve
还是onReject
好,了解完这个核心的概念之后,我们就直接来一版带reject状态的。
3. 带reject状态的(第三版)
代码和注释
const callbacks = [] // 这里用来存储.then的链式调用中所有函数
class MyPromise {
constructor(executor) {
let state = 'pending'
const handle = (params) => {
let _params = params // 这里如果是对象,应该深拷贝,避免影响入参
// 加异步是因为即使放在resolve()后面的同步代码,也会在resolve()之前执行
setTimeout(() => {
for (const item of callbacks) {
switch (state) {
case 'fulfilled':
// 第二次.then中的入参就是上次return的值,所以这里的params直接等于.then函数的执行结果
_params = item.onResolve && item.onResolve(_params)
break
case 'failed':
_params = item.onReject && item.onReject(_params)
break
}
}
}, 0)
}
const resolve = (params) => {
state = 'fulfilled'
handle(params)
}
const reject = (params) => {
state = 'failed'
handle(params)
}
executor(resolve, reject)
}
then(onResolve, onReject) {
callbacks.push({
onResolve,
onReject
})
return this
}
}
测试用例
const myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
const a = 0
if (a > 0) {
console.log(`执行Promise中异步的逻辑`, 1)
resolve(2)
} else {
reject('异常信息处理')
}
}, 500)
})
myPromise
.then(
(res) => {
console.log(`第一次执行then`, res)
return 3
},
(err) => {
console.log(`第一次执行reject`, err)
return 30
}
)
.then(
(subRes) => {
console.log(`第二次执行then`, subRes)
},
(subErr) => {
console.log(`第二次执行reject`, subErr)
}
)
四、总结
一个基本的Promise就三个步骤:
- 把各个回调函数拆解,按执行顺序实现
- 实现链式调用时,增加一个callbacks数组来存储
.then
函数 - 实现reject状态时,增加一个state来判断执行的状态
到这里一个带核心功能的Promise已经实现了。个人感觉看源码其实不需要原封不动地照搬他的实现,而是从核心的原理得到自己的思考。然后自己尝试手动去实现,再对比一下和源码的差异,趋于完善,会得到挺大的收获。
最后,国庆节快乐~
参考
- 《你不知道的JavaScript(中)》第三章-Promise
- 【译】 Promises/A+ 规范
- Promises/A+
- MDN Promise