JS Promise的用法, 以及自己模拟一个Promise

注: 本文中写的类只是为了了解Promise类的内部原理而模拟出来一个, 并不一定符合类似的规范或者效率多么高, 但是基本的功能还是实现了的.

用法

如下, 这是一个传统的使用回调函数的异步代码

function getAnInt(callback) {
    setTimeout(() => {
        callback(81)
    }, 500)
}

function sqrt(n, resolve, reject) {
    setTimeout(() => {
        let res = Math.sqrt(n)
        if (parseInt(res) === res) {
            resolve(Math.sqrt(n))
        } else {
            reject("cannot get an int")
        }
    }, 500)
}

let errHandler = err => console.log("Error " + err)

getAnInt(v1 => {
    console.log(v1)
    sqrt(v1, v2 => {
        console.log(v2)
        sqrt(v2, v3 => {
            console.log(v3)
            sqrt(v3, v4 => {
                console.log(v4)
            }, errHandler)
        }, errHandler)
    }, errHandler)
})

执行结果:

81
9
3
Error cannot get an int

有没有感觉眼花缭乱? 这金字塔状的代码被亲切地称为回调地狱, 下面就是我们的主角Promise上场的时候了, 酱酱酱酱

function getAnInt() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(81)
        }, 500)
    })
}

function sqrt(n) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let res = Math.sqrt(n)
            if (parseInt(res) === res) {
                resolve(Math.sqrt(n))
            } else {
                reject("cannot get an int")
            }
        }, 500)
    })
}


getAnInt().then(v1 => {
    console.log(v1)
    return sqrt(v1)
}).then(v2 => {
    console.log(v2)
    return sqrt(v2)
}).then(v3 => {
    console.log(v3)
    return sqrt(v3)
}).then(v4 => {
    console.log(v4)
}).catch(err => {
    console.log("Error " + err)
})

执行结果:

81
9
3
Error cannot get an int

结果一模一样, 但是这个代码写出来的感觉, 就是要清晰了好多好多好多好多好多好多

介绍

Promise/A+标准中定义了Promise到底是个什么东西, 这里挑出重点部分, 其余的规范如果想看的话点这里去往官网https://promisesaplus.com/

  • promise 含有then方法, 没有规定其它的方法.
  • then方法会返回一个新的promise
  • promise有三个状态, pending(代办), fulfilled(完成)rejected(被拒绝), 状态只能从pending转成另外两个, 然后就不能再转了.
  • 如果onRejected或者onFulfilled返回了一个Promise对象, 需要得出它的结果再传给下一个then方法里对应的地方

因为本文代码中有很多的 resolve, 所以这里的代码使用resolved(被解决)代替fulfilled

为什么没有列出来更多的内容呢, 因为其它的内容大多和兼容性有关, 与这个实现原理关系不是太大, 还有的是到具体实现函数的时候才会用到的规范, 所以我没有列出来

注: catch方法是ES6标准里的, 它的原理是then(null, onRejected)

实现

住: 本文代码不考虑throw, 为了只体现原理, 让代码尽可能更简单.

Promise的构造函数通常传入一个执行者函数, 这个函数里面是异步逻辑, 接受两个参数: resolvereject.

  • 调用resolve(value)就代表方法成功执行了, Promise会把resolve中传入的value传给then方法里的参数

  • 调用reject(reason)就是执行出错了, Promise会把reject中传入的reason传给then方法里的参数

好, 下面开始做点准备工作

const Pending  = 'pending'
const Resolved = 'resolved'
const Rejected = 'rejected'

class MyPromise {}

诶, 这段代码我感觉不用解释了吧? 下面的我会在注释或者是代码块下方说明


class MyPromise {
    constructor(executor) {
        // 状态
        this.status = Pending
        // 正常运行返回的结果
        this.value = null
        // 发生错误的原因
        this.reason = null
        // 详见这段代码块下面写的 注1
        this.onRejected = () => {}
        this.onResolved = () => {}
        
        let resolve = value => {
            // 如果不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Resolved
            this.value = value
            this.onResolved(value)
        }

        let reject = reason => {
            // 如果不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Rejected
            this.reason = reason
            this.onRejected(reason)
        }
        // 见 注2
        executor(resolve, reject)
    }
}
  • 注1: 这是两个被reject或者resolve后调用的回调函数, 我看的别人实现的版本大多是一个数组, 然后调用的时候一个接一个调用里面的函数.

    我认为对同一个promise调用多次then方法的时候很少, 而且本文只是一个思路展示, 并不严格遵守A+规范, 所以这里就直接写了个什么也没干的函数

    在这里也分析一下, 在then方法调用的时候, 如果调用then时的状态是Pending, 那么就设置一下当前对象里的onRejectedonResolved, 具体设置什么在后面的代码里会提到; 如果状态不是Pending, 就代表这两个函数早就执行完了, 就需要根据this.valuethis.reason具体的调用then函数中传进来的onRejectedonResolved.

  • 注2: 这里直接同步调用了, 没有异步调用. 因为如果这个操作真的需要异步的话, 在executor函数里面就会有异步方法了(如setTimeout), 不需要Promise类给它办.


然后就是then方法啦~

注意: then方法要求每次返回新的Promise对象.

先写个框架

then(onResolved, onRejected) {
  let funcOrNull = f => typeof f === "function" ? f : null
  onResolved = funcOrNull(onResolved)
  onRejected = funcOrNull(onRejected)

  if (this.status === Rejected) {
    return new MyPromise((resolve, reject) => {

    })
  } else if (this.status === Resolved) {
    return new MyPromise((resolve, reject) => {

    })
  } else {
    return new MyPromise((resolve, reject) => {

    })
  }
}

这一段应该没什么不好理解的地方, 然后先实现第一个if块里的代码

if (this.status === Rejected) {
    return new MyPromise((resolve, reject) => {
        let value = (onRejected === null ? reject : onRejected)(this.reason)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    })
}

这些实现的代码包括下面的elseif和else块就是最难理解的了, 我当时是好久好久也没有理解, 接下来我会就像数学里面一样分类讨论:


  • 如果调用的时候是这样的:

    new MyPromise((resolve, reject) => {
        reject("I rejected the promise")
    }).then(null, console.log)
    

    先分析构造方法, 创建Promise对象的时候, 这里它的状态就变成Rejected, 但是其他的什么事都没干, 让我们来看前面的代码

    this.onRejected = () => {}
    this.onResolved = () => {}
    
    let reject = reason => {
        if (this.status !== Pending) {
            return
        }
        this.status = Rejected
        this.reason = reason
        this.onRejected(reason)
    }
    executor(resolve, reject)
    

    这个时候this.onRejected还是个空函数, 所以调用它也没什么用


    接下来到then方法了, 让我们来看上面if块里的代码

    return new MyPromise((resolve, reject) => {
        let value = (onRejected === null ? reject : onRejected)(this.reason)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    })
    

    可以看出它执行了let value = onRejected(reason), 然后调用resolve(value), 之后这个新的Promise状态就是Resolved了.

    至于为什么这里要用resolve, 我是通过NodeJS做了个实验看看NodeJS对这件事是怎么干的, 代码如下

    let p1 = new Promise((resolve, reject) => {
        reject("I rejected the promise")
    })
    
    let p2 = p1.then(null, reason => {
        return 'I am from onRejected function'
    })
    
    // 这里是为了不管到底是什么状态都能把p1和p2输出出来
    p2.then(() => console.log(p1, p2), () => console.log(p1, p2))
    

    输出(没有换行, 我为了方便看自己加上的)

    Promise { <rejected> 'I rejected the promise' }
    Promise { 'I am from onRejected function' }
    

    这就看出来NodeJS是在处理完错误之后把onRejected的返回值用resolve函数处理了


  • 如果调用的时候是这样的

    new MyPromise((resolve, reject) => {
        reject("I just rejected the promise")
    }).then(null, null).then(null, console.log)
    

    这个时候就要考虑不能把错误信息丢掉了, 为了实现这个"穿透"功能, 我们可以研究一下NodeJS是怎么干的

    let p1 = new Promise((resolve, reject) => {
        reject("I rejected the promise")
    }).then(null, null)
    
    p1.then(() => console.log(p1), () => console.log(p1))
    

    输出

    Promise { <rejected> 'I rejected the promise' }
    

    这就很简单了, NodeJS是把新的Promise对象继续调用reject并且传递错误信息. 所以再看上面if块里的代码

    return new MyPromise((resolve, reject) => {
        let value = (onRejected === null ? reject : onRejected)(this.reason)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    })
    

    可以看出这里也是在onRejected空的时候直接用reject方法把新的Promise对象的状态设置成了Rejected并且也把this.reason错误信息传了过去.


    你可能会疑惑, 那么reject(this.reason)返回值应该是undefined, 然后又调用了resolve(value)是怎么回事呢?

    这里我们要看前面的代码

    let resolve = value => {
        // 如果不是Pending就忽略
        if (this.status !== Pending) {
            return
        }
        this.status = Resolved
        this.value = value
        this.onResolved(value)
    }
    

    在调用完reject之后, 这里的status就变成了Rejected, 这个方法就不会调用了呀


    你可能还会疑惑, 这里的代码

    if (value instanceof MyPromise) {
        value.then(resolve, reject)
    } else {
        resolve(value)
    }
    

    虽然说你知道返回值是Promise要得出结果, 但是为什么第二行要这么写?

    还是老方法, 我们看看NodeJS这个地方怎么实现的

    let p = new Promise((resolve, reject) => {
        reject("I rejected the promise")
    }).then(null, reason => {
        return new Promise((resolve, reject) => {
            resolve("Hello~")
        })
    }).then(value => {
        console.log("Value " + value)
    }, reason => {
        console.log("Reason " + reason)
    })
    

    运行结果

    Value Hello~
    

    所以说, 这里需要这么写, 让这个then里返回的Promise对象then方法的onResolved方法直接调用新对象的resolvereject方法来操作这个新对象


如果上面的都能理解了, 那么下面这个elseif块就特别好理解了

else if (this.status === Resolved) {
    return new MyPromise((resolve, reject) => {
        let value = (onResolved === null ? resolve : onResolved)(this.value)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    }
} 

else块里, 也就是状态是Pending的时候, 需要做的事情几乎和上面的ifelseif块一样

Promise对象状态是Pending的时候, 不能通过this.valuethis.reason获取值, 但是, 我们可以通过设置this.onRejectedthis.onResolved这两个函数, 因为当Promiseexecutor执行完的时候一定会调用这两个函数中的一个, 并且调用它们的时候都会带上valuereason, 所以这里的代码需要这么写

else {
    return new MyPromise((resolve, reject) => {
        this.onResolved = value => {
            let v = (onResolved === null ? resolve : onResolved)(value)
            if (v instanceof MyPromise) {
                v.then(resolve, reject)
            } else {
                resolve(v)
            }
        }

        this.onRejected = reason => {
            let v = (onRejected === null ? reject : onRejected)(reason)
            if (v instanceof MyPromise) {
                v.then(resolve, reject)
            } else {
                resolve(v)
            }
        }
    })
}

最后加上一个catch方法, 其实就是一个语法糖, 既然ES6都加上了, 那我也加上吧

catch(onRejected) {
    return this.then(null, onRejected)
}

嘿咻, 终于弄完了, 接下来就是实验新对象的时候啦!(这么说好像有点怪怪的呢)

还是文章开头那熟悉的味道

function getAnInt() {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve(81)
        }, 500)
    })
}

function sqrt(n) {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            let res = Math.sqrt(n)
            if (parseInt(res) === res) {
                resolve(Math.sqrt(n))
            } else {
                reject("cannot get an int")
            }
        }, 500)
    })
}


getAnInt().then(v1 => {
    console.log(v1)
    return sqrt(v1)
}).then(v2 => {
    console.log(v2)
    return sqrt(v2)
}).then(v3 => {
    console.log(v3)
    return sqrt(v3)
}).then(v4 => {
    console.log(v4)
}).catch(err => {
    console.log("Error " + err)
})

结果

81
9
3
Error cannot get an int

附: 全代码

const Pending  = 'pending'
const Resolved = 'resolved'
const Rejected = 'rejected'

class MyPromise {
    constructor(executor) {
        // 状态
        this.status = Pending
        // 正常运行返回的结果
        this.value = null
        // 发生错误的原因
        this.reason = null
        // 见 注1
        this.onRejected = () => {}
        this.onResolved = () => {}
        
        let resolve = value => {
            // 如果不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Resolved
            this.value = value
            this.onResolved(value)
        }

        let reject = reason => {
            // 如果不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Rejected
            this.reason = reason
            this.onRejected(reason)
        }
        // 见 注2
        executor(resolve, reject)
    }

    then(onResolved, onRejected) {
        let funcOrNull = f => typeof f === "function" ? f : null
        onResolved = funcOrNull(onResolved)
        onRejected = funcOrNull(onRejected)

        if (this.status === Rejected) {
            return new MyPromise((resolve, reject) => {
                let value = (onRejected === null ? reject : onRejected)(this.reason)
                if (value instanceof MyPromise) {
                    value.then(resolve, reject)
                } else {
                    resolve(value)
                }
            })
        } else if (this.status === Resolved) {
            return new MyPromise((resolve, reject) => {
                let value = (onResolved === null ? resolve : onResolved)(this.value)
                if (value instanceof MyPromise) {
                    value.then(resolve, reject)
                } else {
                    resolve(value)
                }
            })
        } else {
            return new MyPromise((resolve, reject) => {
                this.onResolved = value => {
                    let v = (onResolved === null ? resolve : onResolved)(value)
                    if (v instanceof MyPromise) {
                        v.then(resolve, reject)
                    } else {
                        resolve(v)
                    }
                }

                this.onRejected = reason => {
                    let v = (onRejected === null ? reject : onRejected)(reason)
                    if (v instanceof MyPromise) {
                        v.then(resolve, reject)
                    } else {
                        resolve(v)
                    }
                }
            })
        }
    }

    catch(onRejected) {
        return this.then(null, onRejected)
    }
}

// 测试模块!
function getAnInt() {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve(81)
        }, 500)
    })
}

function sqrt(n) {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            let res = Math.sqrt(n)
            if (parseInt(res) === res) {
                resolve(Math.sqrt(n))
            } else {
                reject("cannot get an int")
            }
        }, 500)
    })
}


getAnInt().then(v1 => {
    console.log(v1)
    return sqrt(v1)
}).then(v2 => {
    console.log(v2)
    return sqrt(v2)
}).then(v3 => {
    console.log(v3)
    return sqrt(v3)
}).then(v4 => {
    console.log(v4)
}).catch(err => {
    console.log("Error " + err)
})

参考: https://zhuanlan.zhihu.com/p/21834559
https://zhuanlan.zhihu.com/p/183801144

上一篇:Noip模拟68 2021.10.4


下一篇:noip模拟68