面试官:请手写实现一个符合规范的简易 Promise
引言
JavaScript 的 Promise
是一种用于处理异步操作的模式,它比传统的回调更加优雅和强大。在面试中也是比较高频的考点。为了更好地理解和掌握 Promise
的内部机制,我们需要尝试自己实现一个简易版本的 Promise
类,命名为 MyPromise
。
分析与实现---基础实现
function a() {
return new Promise((reslove, reject) => {
setTimeout(() => {
console.log('a')
reslove(1)
}, 1000)
// console.log('a')
// reslove(1)
})
}
function b(){
console.log('b')
}
function c(){
console.log('c')
}
// a()
// .then(b)
// .then(c)
// .catch(error => {
// console.log(error)
// })
a()
.then((res) => {
b()
console.log(res)
return res
})
.then((res) => {
c()
console.log(res)
})
.catch((error) => {
console.log(error)
})
我们来分析一下上面的Promise
有哪些功能和细节。
我们可以看到new Promise
中传递了一个函数作为参数,所以我们可以选择用类去模拟实现Promise
,类中的constructor
函数可以接受一个参数进行实例化对象。接受参数后,马上执行。于是我们写出如下:
class MyPromise {
constructor(executor){
executor()
}
}
new MyPromise((resolve, reject)=>{
resolve()
// reject()
})
executor
参数是一个函数,这个函数接收两个参数reslove
和 reject
,这两能够传参和调用,因此constructor
中还需要两个变量且值为函数体,如下:
class MyPromise {
constructor(executor){
const resolve = () => {}
const reject = () => {}
executor(resolve, reject)
}
}
调用a
函数后会返回一个promise
实例对象,而这个实例对象后面可以接.then
方法,因此可以判断Promise
类中会有一个.then
方法,接受一个回调函数作为参数,而.then
方法需要做一件什么事呢?你可能会产生一个误区,认为.then
方法使得接受的回调函数执行,其实不然。当你不写reslove()
或 reject()
时,回调函数不会触发执行,因此.then
方法里的回调函数执不执行完全由reslove()
或 reject()
说的算。那么.then
方法到底干了件什么事呢?
正如所说,回调函数是由reslove()
或 reject()
触发的,因此,回调函数肯定在reslove()
或 reject()
函数体里,不然怎么去触发,所以.then
方法的一个主要功能就是将回调函数存储在一个容器中,然后再把这个容器放入reslove()
或 reject()
调用,因此我们可以在构造函数里两个容器,如下:
class MyPromise {
constructor(executor){
this.onFulfilledCallbacks = [] // 成功的回调函数(.then有多个, 所以有多个回调函数,用数组存储)
this.onRejectedCallbacks = [] // 失败的回调函数
const resolve = () => {}
const reject = () => {}
executor(resolve, reject)
}
then(){
}
}
我们又想到reslove()
和 reject()
是对立的,要么有我没他,要么没他有我,总之一句话,状态一旦发生改变,就不可更改,所以我们需要存储状态的变量并在函数中进行状态判断,如下:
class MyPromise {
constructor(executor){
this.state = 'pending'
this.onFulfilledCallbacks = [] // 成功的回调函数(.then有多个, 所以有多个回调函数,用数组存储)
this.onRejectedCallbacks = [] // 失败的回调函数
const resolve = () => {
if(this.state === 'pending'){
this.state = 'fulfilled'
}
}
const reject = () => {
if(this.state === 'pending'){
this.state = 'rejected'
}
}
executor(resolve, reject)
}
then(){
}
}
我们还想到reslove()
和 reject()
会接受一个参数,这个参数就是.then()
中的回调函数上的参数,所以我们应该将reslove()
和 reject()
上的参数记录下来以便传递给.then
方法,于是我们又这样写:
class MyPromise {
constructor(executor){
this.state = 'pending'
this.value = undefined // 记录resolve的参数
this.reason = undefined // 记录reject的参数
this.onFulfilledCallbacks = [] // 成功的回调函数(.then有多个, 所以有多个回调函数,用数组存储)
this.onRejectedCallbacks = [] // 失败的回调函数
const resolve = (value) => {
if(this.state === 'pending'){
this.state = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(cb => cb(value)) // 顺便把这步补上
}
}
const reject = () => {
if(this.state === 'pending'){
this.state = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(cb => cb(reason)) // 顺便把这步补上
}
}
executor(resolve, reject)
}
then(){
}
}
三、分析与实现---.then方法实现
现在就剩then()
函数的实现了,我们之前分析过,他的主要功能是存储回调函数,但还有其他的功能和细节,我们来分析一下:
Promise
后面可以接着写多个then
方法,所以要想写两个以上的then
方法,那么then
函数里肯定是需要返回一个Promise
另外一个方面,then
函数中需要传两个参数即成功的回调函数和失败的回调函数,在这里要注意一下,如果传入的参数是非函数体,那么就需要做出响应,因此就需要判断,如下:
then (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
return new MyPromise((resolve, reject) => {
})
}
.then
做出的动作因状态而改变,有三种状态,就有三种情况,我们分别进行分析:
1. fulfilled状态
当Promise中的内容为非异步任务时,那么意味着任务会马上执行,那么resolve
马上会执行,state
状态也会变更为fulfilled
,而此时.then
还没有将回调函数给resolve
,因此回调函数并没触发,但真实的情况是回调函数还是会触发,鉴于这种情况,我们需要做出如下改变:
当state
状态为fulfilled
时,直接触发回调函数执行,如果回调函数有返回值给下一个.then
,就需要传参。
if(this.state === 'fulfilled'){
const res = onFulfilled(this.value)
resolve(res)
}
这里有个细节,.then
是微任务,所以我们需要把它变为微任务,但实现起来过于复杂,所以我用宏任务setTimeout
去模拟微任务......,另外顺便用try处理一下意外错误:
if(this.state === 'fulfilled'){
setTimeout( () => { // 模拟异步执行,但是模拟不了微任务
try{
const res = onFulfilled(this.value)
resolve(res)
}catch(error){
reject(error)
}
})
}
2. rejected状态
逻辑和上面的一样,如下代码:
if(this.state === 'rejected'){
setTimeout( () => {
try{
const res = onRejected(this.reason)
resolve(res)
}catch(error){
reject(error)
}
})
}
3. pending状态
这种状态就是正常状态了,将回调函数放入容器中,顺便模拟为微任务如下:
if(this.state === 'pending'){
// 将自己的回调存进onFulfilledCallbacks和onRejectedCallbacks中
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
onFulfilled(value)
})
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
onRejected(reason)
})
})
}
四、附上整体实现代码:
class MyPromise{
constructor(executor){
this.status = 'pending'
this.value = undefined // 临时保存 resolve 的参数
this.reason = undefined // 临时保存 reject 的参数
this.onFulfilledCallbacks = [] // 成功的回调函数
this.onRejectedCallbacks = [] // 失败的回调函数
const resolve = (value) => {
if(this.status === 'pending'){
this.status = 'fulfilled'
this.value = value
this.onFulfilledCallbacks.forEach(cb => cb(value))
}
}
const reject = (reason) => {
if(this.status === 'pending'){
this.status = 'rejected'
this.reason = reason
this.onRejectedCallbacks.forEach(cb => cb(reason))
}
}
executor(resolve, reject)
}
then (onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}
return new MyPromise((resolve, reject) => {
if(this.state === 'fulfilled'){
setTimeout( () => { // 模拟异步执行,但是模拟不了微任务
try{
const res = onFulfilled(this.value)
resolve(res)
}catch(error){
reject(error)
}
})
}
if(this.state === 'rejected'){
setTimeout( () => {
try{
const res = onRejected(this.reason)
resolve(res)
}catch(error){
reject(error)
}
})
}
if(this.state === 'pending'){
// 将自己的回调存进onFulfilledCallbacks和onRejectedCallbacks中
this.onFulfilledCallbacks.push((value) => {
setTimeout(() => {
onFulfilled(value)
})
})
this.onRejectedCallbacks.push((reason) => {
setTimeout(() => {
onRejected(reason)
})
})
}
})
}
}
五、结语
写的不好,理解起来会有点绕,但只要多加理解,肯定是没问题,面试中关于Promise的话题是永久不息的,希望你我能克服难题,技术更上一层楼,感谢阅读!!!
转载自:https://juejin.cn/post/7392848248488067087