前言
秋招来临,笔者也开始复盘起知识点,而Promise的使用以及原理在面试中经常被问到,笔者复习完这部分知识后,仍觉得不够通透,故写出这篇文章来加深理解,也希望对读者有些许帮助。
本篇文章将带你一步一步的深入,用六十行代码帮助你实现一个面试够用的Promise。
实现 resolve 和 reject
在动手之前,我们先看看Promise的特点,再得出我们需要实现的效果
let p1 = new Promise((resolve, reject) => {})
console.log('p1', p1)
let p2 = new Promise((resolve, reject) => {
resolve('成功')
reject('失败')
})
console.log('p2', p2)
let p3 = new Promise((resolve, reject) => {
reject('失败')
resolve('成功')
})
console.log('p3', p3)
很明显,原生的Promise执行resolve,Promise状态会变成fulfilled;执行reject, Promise状态会变成rejected;如果不执行resolve或reject,Promise状态是pending。
Promise只以第一次执行为准,状态修改后无法再次修改,即Promise只能使用一次resolve或reject。
了解完这些,我们来实现在Promise里使用resolve和reject的效果。
第一步,实现 resolve 和 reject
明确需求:
- 执行
resolve, Promise状态会变成fulfilled。 - 执行
reject, Promise状态会变成rejected。 - 如果不执行
resolve或reject,Promise状态是pending。 - Promise只以第一次执行为准,状态修改后无法再次修改,即Promise只能使用一次
resolve或reject,后面再使用无效。
带着这个问题我们开始构建myPromise
// Promise的三种状态
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
constructor(executor) {
// 成功值
this.value = null
// 失败的原因
this.reason = null
// 初始状态为pending
this.state = PENDING
// 初始化this指向
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
// 执行传入的函数
executor(this.resolve, this.reject)
}
resolve(value) {
// 如果状态已经改变则返回
if (this.state !== PENDING) return
// 取到resolve里的参数
this.value = value
// 修改状态
this.state = FULFILLED
}
reject(reason) {
// 如果状态已经改变则返回
if (this.state !== PENDING) return
// 取到resolve里的参数
this.reason = reason
// 修改状态
this.state = REJECTED
}
}
我们来看看是否达成了我们的需求
const test1 = new myPromise((resolve, reject) => {
resolve('成功')
resolve('再次成功')
})
console.log(test1)
// myPromise {value: "成功", reason: null, state: "fulfilled", resolve: ƒ, reject: ƒ}
const test2 = new myPromise((resolve, reject) => {
reject('失败')
resolve('成功')
})
console.log(test2)
// myPromise {value: "null", reason: "失败", state: "rejected", resolve: ƒ, reject: ƒ}
很明显,目前myPromise有且只有一次状态修改,也就是说只可调用一次resolve或reject,再次调用resolve或reject无效。
第二步,reject 捕获错误
如果Promise中有throw的话,就相当于执行了reject
let p4 = new Promise((resolve, reject) => {
throw ('错误')
})
console.log('p4', p4)
明确需求:
Promise中如果有throw的话,就相当于执行了reject,即reject可以用来捕获错误,这里我们使用try/catch解决,我们去修改constructor里的代码。
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
验证
const test3 = new myPromise((resolve, reject) => {
throw ('失败')
})
console.log(test3) // myPromise {value: "null", reason: "失败", state: "rejected", resolve: ƒ, reject: ƒ}
可以看到我们成功用reject捕获了错误并输出错误,并实现了resolve和reject的逻辑。接下来我们来实现.then。
实现 .then
我们先看看.then最基本的使用。
const p5 = new Promise((resolve, reject) => {
resolve('成功')
}).then(res => console.log(res), err => console.log(err))
// 成功
const p6 = new Promise((resolve, reject) => {
reject('失败')
}).then(res => console.log(res), err => console.log(err))
// 失败
很明显,.then接收两个回调,一个成功回调一个失败回调,当Promise状态为fulfilled时执行成功回调,当Promise状态为rejected时执行失败回调,了解这些后我们进入第三步。
第三步,实现 .then 的基本使用
明确需求:
.then接收两个回调,一个成功回调一个失败回调- 当Promise状态为
fulfilled时执行成功回调 - 当Promise状态为
rejected时执行失败回调 明确这三点我们接着写代码:
then(onFulfilled, onRejected) {
// 校验两个参数是函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
// 如果状态是fulfilled,执行onFulfilled回调函数
if (this.state === FULFILLED) {
// 传入this.value也就是resolve的参数
onFulfilled(this.value)
} else if (this.state === REJECTED) {
// 传入this.reason也就是reject的参数
onRejected(this.reason)
}
}
检验是否实现需求
const test4 = new myPromise((resolve, reject) => {
resolve('成功')
}).then(res => console.log(res), err => console.log(err))
// 成功
const test5 = new myPromise((resolve, reject) => {
reject('失败')
}).then(res => console.log(res), err => console.log(err))
// 失败
到这里我们就完成了第三步的需求,但是这一步只是模拟了.then的效果,并没有实现.then的真正功能。
Promise实现异步功能就是因为.then必须在resolve或reject执行后才能执行回调函数,实现异步。可能有的读者认为: 这里状态不是先被改变,然后.then再执行的吗? 的确,这里确实是先resolve或reject改变状态后再执行.then的,但如果换种情况,你会发现我们并没有做到.then一定在状态改变后执行。
const test6 = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
}, 1000)
}).then(res => console.log('回调执行', res), err => console.log('回调执行', err))
// 无输出,证明.then在resolve('成功')之前执行,导致里面的回调没有调用
我们来分析一下,这里用setTimeout延迟执行resolve,在setTimeout还没走完时,就已经执行完.then了,而此时resolve尚未执行,所以.then里的回调函数状态没有被改变,无法执行。等resolve('成功')执行完后,.then在一秒前已经执行过了,所以我们要等待resolve或者reject的执行,resolve或rejec执行完后,再进入.then。接下来,我们进入第四步。
第四步,定时器情况
显然第四步我们必须实现.then在reslove或reject改变状态后再执行。
明确需求:
- 如果执行
then时,发现状态是pending,说明此时reslove和reject尚未执行,先将.then里的回调函数保存起来。 - 等到myPromise里
reject或resolve执行时,再将回调函数取出去执行,此时其实.then已经执行完了,回调函数放在resolve或reject里执行。 - 用数组保存回调,因为一个myPromise实例可能会多次
.then,用数组就可以全部保存了。
实现
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
constructor(executor) {
this.value = null
this.reason = null
this.state = PENDING
// 保存then中的成功回调
this.onFulfilledCallbacks = []
// 保存then中的失败回调
this.onRejectedCallbacks = []
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
resolve(value) {
if (this.state !== PENDING) return
this.value = value
this.state = FULFILLED
// 调用onFulfilledCallbacks里保存的onFulfilled的回调
this.onFulfilledCallbacks.map(cb => cb(value))
}
reject(reason) {
if (this.state !== PENDING) return
this.reason = reason
this.state = REJECTED
// 调用onRejectedCallbacks里保存的then的onRejected回调
this.onRejectedCallbacks.map(cb => cb(reason))
}
then(onFulfilled, onRejected) {
// 保持指针指向
const that = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
if (this.state === FULFILLED) {
onFulfilled(this.value)
} else if (this.state === REJECTED) {
onRejected(this.reason)
} else { // 状态为pending时
// 保存回调函数
that.onFulfilledCallbacks.push(onFulfilled)
that.onRejectedCallbacks.push(onRejected)
}
}
}
检验是否实现需求
const test7 = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve('成功')
}, 1000)
}).then(res => console.log('回调执行', res), err => console.log('回调执行', err))
// 回调执行 成功
可以看见我们实现了.then在reslove或reject改变状态后执行。接下来我们进入第五步。
第五步,链式调用
.then支持链式调用,下一次.then的执行受上一次返回值的影响
const p7 = new Promise((resolve, reject) => {
resolve(8)
}).then(res => 3 * res) // 第一个.then ,成功回调结果return出去,返回的不是Promise对象
.then(res => console.log(res)) // 第二个.then里得到第一次成功回调里返回的值
// 24
const p8 = new Promise((resolve, reject) => {
resolve(24)
}).then(res => new Promise((resolve, reject) => resolve(2.5 * res))) // 返回一个Promies对象
.then(res => console.log(res))
// 60
根据原生Promise.then的效果明确需求:
.then方法本身会返回一个新的Promise对象,因为.then的返回值可以被.then。- 如果
.then的回调函数里返回值不是Promise对象,那么.then返回的新Promise对象状态就是fulfilled,resolve传入的值为回调函数里返回的值。 - 如果
.then的回调函数里返回值是Promise对象,.then返回的新Promise对象会自动执行掉它里面的resolve或reject。
接下来我们来实现
then(onFulfilled, onRejected) {
// 保持指针指向
const that = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
// 返回一个新Promise
return new myPromise((resolve, reject) => {
if (that.state === FULFILLED) {
// 获取成功回调函数的执行结果
const result = onFulfilled(that.value);
// 如果.then的回调函数里返回值是myPromise对象,自动帮它执行resolve或reject
if (result instanceof myPromise) {
result.then(
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
} else {
// .then里返回的不是promise函数,则在当前.then返回的promise里执行resolve
resolve(result)
}
} else if (that.state === REJECTED) {
onRejected(that.reason)
} else {
that.onFulfilledCallbacks.push(onFulfilled)
that.onRejectedCallbacks.push(onRejected)
}
})
}
检验是否实现需求
const test8 = new Promise((resolve, reject) => {
resolve(8)
}).then(res => 3 * res, err => console.log(err))
.then(res => console.log(res), err => console.log(err))
//24
const test9 = new Promise((resolve, reject) => {
resolve(24)
}).then(res => new Promise((resolve, reject) => resolve(2.5 * res)), err => console.log(err))
.then(res => console.log(res), err => console.log(err))
//60
可以看到我们已经实现了链式调用。
第六步,微任务
如果你了解过js执行机制,那就应该知道.then是微任务,它一定是在一次宏任务执行完后执行的,如果不清楚也没关系,我们来看一个例子
// 原生Promise
const p9 = new Promise((resolve, reject) => {
resolve(24)
}).then(res => console.log(res), err => console.log(err))
console.log(8)
// 8 24
// myPromise
const test10 = myPromise((resolve, reject) => {
resolve(24)
}).then(res => console.log(res), err => console.log(err))
console.log(8)
// 24 8
原生Promise中先输出 2 再输出 1 ,.then的回调在外部console.log之后。可见在Promise中.then是个微任务,它一定是在当前宏任务执行完后执行的。
这里我们借助setTimeout来完善一下myPromise,其实setTimeout也属于宏任务,但它不会进入第一次宏任务队列,这里的逻辑就牵扯到事件循环,与本文内容关系不大就不再细说,我们只需要知道借助setTimeout不进入第一次宏任务队列的特性可以帮助我们实现.then是微任务的效果。
then(onFulfilled, onRejected) {
// 保持指针指向
const that = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
return new myPromise((resolve, reject) => {
if (that.state === FULFILLED) {
// 调用setTimeout
setTimeout(() => {
const result = onFulfilled(that.value);
if (result instanceof myPromise) {
result.then(
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
} else {
resolve(result)
}
}, 0)
} else if (that.state === REJECTED) {
setTimeout(() => {
onRejected(that.reason)
}, 0)
} else {
that.onFulfilledCallbacks.push(onFulfilled)
that.onRejectedCallbacks.push(onRejected)
}
})
}
查看结果
const test10 = new myPromise((resolve, reject) => {
resolve(1)
}).then(res => console.log(res), err => console.log(err))
console.log(2)
// 2 1
由此我们看到实现了原生Promise里.then的效果。
总体代码
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class myPromise {
constructor(executor) {
this.value = null
this.reason = null
this.state = PENDING
this.onFulfilledCallbacks = []
this.onRejectedCallbacks = []
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}
resolve(value) {
if (this.state !== PENDING) return
this.value = value
this.state = FULFILLED
this.onFulfilledCallbacks.map(cb => cb(value))
}
reject(reason) {
if (this.state !== PENDING) return
this.reason = reason
this.state = REJECTED
this.onRejectedCallbacks.map(cb => cb(reason))
}
then(onFulfilled, onRejected) {
const that = this
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err
}
return new myPromise((resolve, reject) => {
if (that.state === FULFILLED) {
setTimeout(() => {
const result = onFulfilled(that.value);
if (result instanceof myPromise) {
result.then(
value => {
resolve(value)
},
reason => {
reject(reason)
}
)
} else {
resolve(result)
}
}, 0)
} else if (that.state === REJECTED) {
setTimeout(() => {
onRejected(that.reason)
}, 0)
} else {
that.onFulfilledCallbacks.push(onFulfilled)
that.onRejectedCallbacks.push(onRejected)
}
})
}
}
想说的话
Promise原理实现其实并不难,本文只是简单实现了.then的部分功能。其余的API实现就不再细说,读者可以自行翻阅文档。
自从复盘以来,我觉得其实手写原理关键在于明白需要完成哪些功能,再一步一步的去实现它,从易到难,修改漏洞,并且实现顺序也很重要(笔者手写时计划先实现微任务功能后再实现链式调用,结果因为setTimeout的问题卡了很久,待将实现顺序对调后,就顺理成章的实现了需求。。。),说到这也就差不多了。如果有任何不懂的或者发现本文的错误地方,欢迎留言评论指正。
如果本文对你有所帮助的话,期待你的点赞。