Basic implementation of handwritten Promise (super detailed)

Table of contents

One: First analyze the official promise

Two: Handwritten Promise-then method design

Three: then method optimization:

Four: Promise-catch method design

 Five: Promise-finally method design


//This article takes you to realize a basic promise. Too many boundary conditions are not a test, just understand the main implementation process and logic

//For the problems that arise one by one, I will gradually analyze the reasons and corresponding solutions

//The premise is that you must master the basic use of promises and have a corresponding understanding of callback functions

//Step 1: Preliminary implementation of the simplest Promise (write it down step by step, Xiaobai can understand it, of course, you can skip some nonsense directly~~~)

One: First analyze the official promise

//---首先分析官方的promise

const p1 = new Promise((resolve, reject) => {
    resolve(2222)
    reject(33333)
})
// 调用then方法 then 方法接收2个回调函数作为参数
p1.then(res => { //只会打印222, 因为promise的状态一但由pending的状态改变了 就无法再次改变
    console.log("res1:", res)
}, err => {
    console.log("err:", err)
})

//Analysis:

//It can be seen that the parameter of promise receives a function, and receives 2 callback functions as parameters in the function, and then calls the resolve method

//Is executing the then method. The then function receives two parameters, both of which are callback functions. After executing resolve(2222), it will come to the callback function of the first parameter of then

// After executing reject(33333), it will come to the second parameter callback function of then-

//1. Then the preliminary writing can be obtained:

const PROMISE_STATUS_PENDING = 'pending' //定义三种状态常量
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'


class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING //1.初始化状态 pending
        this.value = undefined //2.保存参数
        this.error = undefined
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) { //3. 只有pending状态才可改变
                this.status = PROMISE_STATUS_FULFILLED
            }
        })


        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED


            }
        })


        executor(resolve, reject) //在new myPromise 时初始化立即调用传递过来的函数
    }
}

Two: Handwritten Promise-then method design

//2. Executing resolve or reject will call the then method (changed on the basis of the above code)

class myPromise {
    constructor(executor) {


        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED


                this.resfn(value)
            }
        })


        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED


                this.errfn(error)
            }
        })


        executor(resolve, reject)
    }


    then(resfn, errfn) { //4.then 方法接收2个回调函数作为 参数 : 第一个为成功的回调,第二个失败的回调
        this.resfn = resfn
        this.errfn = errfn
    }
}

//(See if the code is the most basic implementation) Next, we execute the promise we implemented to see if it is normal

 const p1 = new myPromise((resolve, reject) => {

    resolve(111)

})

p1.then(res => {

console.log(res);

}, err => {

})

//After execution, you will find an error message: this.resfn is not a function

//So why is it reporting an error? Because it is related to the execution order of the code, when we execute resolve in new myPromise(), we will come to the resolve method

//And your then method duck soup is not executed, so it reports an error

//Solution: Just let then be executed first, and add resolve or reject to the asynchronous queue when the then method is to be executed

Above code:


class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED
                //大家可能会想到使用setTimeout 然后0秒执行,但此处使用queueMicrotask 会更加合适
                // setTimeout(() => {
                //     this.resfn(value)
                // }, 0);

                queueMicrotask(() => { //queueMicrotask:  主线程执行完毕之后立马执行
                    this.resfn(value)
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                queueMicrotask(() => {
                    this.errfn(error)
                })
            }
        })
        executor(resolve, reject)
    }

    then(resfn, errfn) { //4.then 方法接收2个回调函数作为 参数 : 第一个为成功的回调,第二个失败的回调
        this.resfn = resfn
        this.errfn = errfn
    }
}


//举一个栗子:
setTimeout(() => {
    console.log('setTimeout');
}, 0);


queueMicrotask(() => {
    console.log('queueMicrotask');
});


//实际运行
queueMicrotask
setTimeout

Then execute:

const p1 = new myPromise((resolve, reject) => {

    resolve(111)

    reject(333333)

})

p1.then(res => { // final print 1111

    console.log(res);

}, err => {

    console.log(err);

})

So far our simplest implementation has been completed, and then we continue to optimize the then method :

Optimization one:
(then of the official Promise can be called multiple times)
// Our own implementation of mypromise calls the then method multiple times, which we currently do not support 
p1.then(res => {
    console.log("res1:", res)
}, err => {
    console.log("err1:", err)
})
// Call then method to call multiple times
p1.then(res => {
    console.log("res2:", res)
}, err => {
    console.log("err2:", err)
})
Running result: res2: 111 Because the following .then overwrites the previous one, the code block where res1 is located will not be executed
*It can be seen that when the then method is called, it should be an array and then called in turn
Let's modify our code below:
Above code:
class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined

        this.resfns = [] //1.多次调用then 时用数组 保存
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_FULFILLED
                queueMicrotask(() => {
                    this.value = value
                    this.resfns.forEach(fn => {        //循环依次调用
                        fn(this.value)
                    })
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                this.status = PROMISE_STATUS_REJECTED
                queueMicrotask(() => {
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }

    then(resfn, errfn) {        //将多次调用的then 方法保存到数组中,依次调用
        this.resfns.push(resfn)
        this.errfns.push(errfn)
    }
}
Then execute:
const p1 = new myPromise((resolve, reject) => {
    resolve(111)
    reject(333333)
})
p1.then(res => {
    console.log("res1:", res)
}, err => {
    console.log("err1:", err)
})
// Call then method to call multiple times
p1.then(res => {
    console.log("res2:", res)
}, err => {
    console.log("err2:", err)
})
Results of the:
res1: 111
res2: 111
Does this solve the problem of our multiple calls, then look carefully at our code
In the resolve function, we only execute the then callback function when its status is pending
So if when we execute then, its status is no longer pending, then it will not be executed?
Then some friends may ask: "Hey, how could its state have been changed when executing then? I clearly saw that the code you wrote pushed its success callback and failure callback into the array. And then execute it sequentially?"
It may be more abstract: Next, we use code to reproduce this problem
const promise = new HYPromise((resolve, reject) => {
  console.log("status pending")
  resolve(1111) 
  reject(2222)
})
// Call then method to call multiple times
promise.then(res => {
  console.log("res1:", res)
}, err => {
  console.log("err:", err)
})
promise.then(res => {
  console.log("res2:", res)
}, err => {
  console.log("err2:", err)
})
/** Look carefully here. The above code is synchronous. The execution will be added to the corresponding array and called sequentially. After the call is completed, is his state determined? */
//Then you execute the following asynchronous code, then nothing will be executed at this time because your state has been changed, and you have already  this.resfns.push(resfn) this function, but it is not at all will not execute
setTimeout(() => {
  promise.then(res => {
    console.log("res3:", res)
  }, err => {
    console.log("err3:", err)
  })
}, 1000)
Results of the:
res1: 111
res2: 111
It can be seen that the third then is not executed
Solutions:
If the state has been determined when then is called, then the callback in then should be executed directly
Above code:
class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined

        this.resfns = [] //1.多次调用then 时用数组 保存
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                   
                        fn(this.value)
                    })
                })
            }
        })


        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })

        executor(resolve, reject)
    }

    then(resfn, errfn) {
        // 1.如果在then调用的时候, 状态已经确定下来,那么就可以直接调用
        if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
            resfn(this.value)
        }
        if (this.status === PROMISE_STATUS_REJECTED && errfn) {
            errfn(this.error)
        }

        //2. 相反 对与已经确定的状态 就不需要在push 执行了
        if (this.status === PROMISE_STATUS_PENDING) {
            this.resfns.push(resfn)
            this.errfns.push(errfn)
        }

    }
}

implement:

const p1 = new myPromise((resolve, reject) => {

    resolve(111)

    // reject(333333)

})

p1.then(res => {

    console.log("res1:", res)

}, err => {

    console.log("err1:", err)

})

// Call then method to call multiple times

p1.then(res => {

    console.log("res2:", res)

}, err => {

    console.log("err2:", err)

})

// After determining the state of the Promise, call then again

setTimeout(() => {

    p1.then(res => {

        console.log("res3:", res)

    }, err => {

        console.log("err3:", err)

    })

}, 1000)

result:

res1: 111

res2: 111

res3:111

Three: then method optimization:

For our previous then method, multiple calls are called through the instantiated class

For example:

p1.then(res => {

    console.log("res1:", res)

}, err => {

    console.log("err1:", err)

})

p1.then(res => {

    console.log("res2:", res)

}, err => {

    console.log("err2:", err)

})

This kind of call is achievable with our current code, but what if it is called in a chain all the time? Let's talk nonsense and go to the code:

const p1 = new myPromise((resolve, reject) => {

    resolve(111)

    reject(333333)

})

p1.then(res => {

    console.log("The first successful callback:", res)

    return "Second success callback"

}, err => {

    console.log("err1:", err)

    return "Second failure callback"

}).then(res => {

    console.log("res2:", res)

}, err => {

    console.log("err2:", err)

})

Now our code will definitely not work (you can test it in your own environment) Next, continue to optimize our code:

Before optimizing, first analyze a wave: (The official promise chain call is used as an example to explain)

1. Execute resolve 

2. Call the then method, the value returned by the then method is actually new myPromse((resolve,reject) => resolve("The second successful callback")), and the return value is passed on with the resolve() method

So after printing "the first successful callback:", 111, print "res2:" again, the second successful callback,

3. So when will the "err2:" second failure callback be printed? It only needs to throw an exception when calling then for the first time, and err2 will be executed.

For example:

p1.then(res => {

    console.log("The first successful callback:", res)

    throw new Error("err message")

}, err => {

    console.log("err1:", err)

    return "Second failure callback"

}).then(res => {

    console.log("res2:", res)

}, err => {

    console.log("err2:", err)

})

Results of the:

"First successful callback:", 111

"err2: Error: err message

2. If reject is executed, in the second callback of reject 

err => {

    console.log("err1:", err)

    return "Second failure callback"

}

 If the value is returned, will console.log("res2:", res) or console.log("err2:", err) be executed next?

For example:

const p1 = new myPromise((resolve, reject) => {

    reject(333333)

})

p1.then(res => {

    console.log("The first successful callback:", res)

    return "Second success callback"

}, err => {

    console.log("err1:", err)

    return "Second failure callback"

}).then(res => {

    console.log("res2:", res)

}, err => {

    console.log("err2:", err)

})

Here I will tell you that console.log("res2:", res) will actually be executed. If you want to execute the code block locked by console.log("err2:", err), you need to return "the second failure callback "Change to throw an exception to execute

chestnut:

p1.then(res => {

    console.log("The first successful callback:", res)

    return "Second success callback"

}, err => {

    console.log("err1:", err)

   throw new Error("err message")

}).then(res => {

    console.log("res2:", res)

}, err => {

    console.log("err2:", err)

})

The logic has been clarified and then expressed in code: (you can copy the following code, and then test it yourself with the above case to make it clearer)

class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        this.resfns = []
        this.errfns = []
        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return //避免 调用resolve 后又调用 reject 多次执行
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                        fn(this.value)
                    })
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }


    then(resfn, errfn) {
        return new myPromise((resolve, reject) => { //1. 直接new 一个mypromise 作为then 方法的返回值 既可实现 .then.then.thne.then.....等等链式调用
            if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
                try { //2. 异常处理:若成功则继续执行then链式调用 的第一个回调,失败则执行then 的第二个回调
                    const value = resfn(this.value)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }


            if (this.status === PROMISE_STATUS_REJECTED && errfn) {
                try {
                    const value = errfn(this.error)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }


            if (this.status === PROMISE_STATUS_PENDING) {
                this.resfns.push(() => { //push 回调函数
                    try {
                        const value = resfn(this.value)
                        resolve(value)
                    } catch (err) {
                        reject(err)
                    }
                })
                this.errfns.push(() => {
                    try {
                        const value = errfn(this.error)
                        resolve(value)
                    } catch (err) {
                        reject(err)
                    }
                })
            }
        })
    }
}

//然后测试:
const p1 = new myPromise((resolve, reject) => {
    // resolve(111)
    reject(333333)
})

p1.then(res => {
    console.log("res1:", res)
    return "第二次的成功回调"
}, err => {
    console.log("err1:", err)
     throw new Error("err message")
   // return "第二次的失败回调"
}).then(res => {
    console.log("res2:", res)
}, err => {
    console.log("err2:", err)
})
result:
err1: 333333
err2: Error: err message
OK perfect solution 

Four: Promise-catch method design

With the above code we have implemented a basic promise
At this point, setting the catch method is very simple.
Above code:
class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        this.resfns = []
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return //避免 调用resolve 后又调用 reject 多次执行
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                        fn(this.value)
                    })
                })
            }
        })

        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }


    then(resfn, errfn) {
        // 1.难点:利用抛错让下一个promise的catch帮忙处理  防止catch方法让链式调用断层
        const defaultOnRejected = err => {
            throw err
        }
        errfn = errfn || defaultOnRejected


        return new myPromise((resolve, reject) => {


            if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
                try { //2. 异常处理:若成功则继续执行then链式调用 的第一个回调,失败则执行then 的第二个回调
                    const value = resfn(this.value)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }
            if (this.status === PROMISE_STATUS_REJECTED && errfn) {
                try {
                    const value = errfn(this.error)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }


            if (this.status === PROMISE_STATUS_PENDING) {
                if (resfn) {
                    this.resfns.push(() => { //push 回调函数
                        try {
                            const value = resfn(this.value)
                            resolve(value)
                        } catch (err) {
                            reject(err)     //tips:****利用抛出的错误 使得下一个promise的catch帮忙处理
                        }
                    })
                }
                if (errfn) {
                    this.errfns.push(() => {
                        try {
                            const value = errfn(this.error)
                            resolve(value)
                        } catch (err) {
                            reject(err)
                        }
                    })
                }
            }
        })
    }
    catch (errfn) { //2.catch 方法
        return this.then(undefined, errfn)
    }
}

//接着测试代码:
const p1 = new myPromise((resolve, reject) => {
    reject(333333)
})

p1.then(res => {
    console.log("res:", res)
}).catch(err => {
    console.log("err:", err)
})

//结果:
err: 333333
A difficult point involved here is to assign a default function to another parameter when only one parameter is passed in the callback of the first p1.then 
And this function must throw an exception to be caught by the catch of the marked tips code, and then reject(err) can be used.catch
Of course if you want to call
p1.catch(err => {
    console.log("err:", err)
}).then(res => {
    console.log("res:", res)
})
You only need to reassign the default function to let the code block resolve(value) in the try receive
Just add this line of code in the first line of comments
const defaultOnFulfilled = value => {
            return value
        }
resfn = resfn || defaultOnFulfilled

 Five: Promise-finally method design

//finally The function will be called regardless of success or failure, and it is called at the end
//Add a finally method directly on the class to take advantage of queueMicrotask's fastest processing of microtasks, so that the function fn passed by finally(fn) is finally executed
  finally(fn) {
        setTimeout(() => {
            fn()
        }, 0)
    }
Finally attach all the code:
const PROMISE_STATUS_PENDING = 'pending' //定义三种状态常量
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'


class myPromise {
    constructor(executor) {
        this.status = PROMISE_STATUS_PENDING
        this.value = undefined
        this.error = undefined
        this.resfns = []
        this.errfns = []

        const resolve = ((value) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return //避免 调用resolve 后又调用 reject 多次执行
                    this.status = PROMISE_STATUS_FULFILLED
                    this.value = value
                    this.resfns.forEach(fn => {
                        fn(this.value)
                    })
                })
            }
        })
        const reject = ((error) => {
            if (this.status === PROMISE_STATUS_PENDING) {
                queueMicrotask(() => {
                    if (this.status !== PROMISE_STATUS_PENDING) return
                    this.status = PROMISE_STATUS_REJECTED
                    this.error = error
                    this.errfns.forEach(fn => {
                        fn(this.error)
                    })
                })
            }
        })
        executor(resolve, reject)
    }


    then(resfn, errfn) {
        // 1.利用抛错让下一个promise的catch帮忙处理  防止catch方法让链式调用断层
        const defaultOnRejected = err => {
            throw err
        }
        errfn = errfn || defaultOnRejected


        const defaultOnFulfilled = value => {
            return value
        }
        resfn = resfn || defaultOnFulfilled


        return new myPromise((resolve, reject) => { //1. 直接new 一个mypromise 作为then 方法的返回值 既可实现 .then.then.thne.then.....等等链式调用

            if (this.status === PROMISE_STATUS_FULFILLED && resfn) {
                try { //2. 异常处理:若成功则继续执行then链式调用 的第一个回调,失败则执行then 的第二个回调
                    const value = resfn(this.value)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }
            if (this.status === PROMISE_STATUS_REJECTED && errfn) {
                try {
                    const value = errfn(this.error)
                    resolve(value)
                } catch (err) {
                    reject(err)
                }
            }
            if (this.status === PROMISE_STATUS_PENDING) {
                if (resfn) {
                    this.resfns.push(() => { //push 回调函数
                        try {
                            const value = resfn(this.value)
                            resolve(value)
                        } catch (err) {
                            reject(err) //tips:****利用抛出的错误 使得下一个promise的catch帮忙处理
                        }
                    })
                }
                if (errfn) {
                    this.errfns.push(() => {
                        try {
                            const value = errfn(this.error)
                            resolve(value)
                        } catch (err) {
                            reject(err)
                        }
                    })
                }
            }
        })
    }

    catch (errfn) { //2.catch 方法
        return this.then(undefined, errfn)
    }
   
    finally(fn) {
        setTimeout(() => {
            fn()
        }, 0)
    }
}

The rest are relatively simple class methods. I won’t talk about it here~~bye~

Guess you like

Origin blog.csdn.net/qq_48554377/article/details/126992966