美文网首页
Promise内部实现原理(个人笔记)

Promise内部实现原理(个人笔记)

作者: kevision | 来源:发表于2020-07-13 00:03 被阅读0次

    本文参考:剖析 Promise 内部机制

    Promise 是一种对异步操作的封装,在异步操作执行成功或者失败时执行指定方法。将横向的异步调用转换为纵向,因此更符合人类的思维方式。

    三种生命状态

    一个 Promise 对象具备三种生命状态:pendingfulfilledrejected。只能从最初的 pending 到 fulfilled 或者 rejected,而且状态的改变是不可逆的

    image.png

    工作原理

    我们先简单看下 Promise 的工作原理。


    image.png

    Promise 大致的工作流程是

    • 创建 Promise 对象 => 进入等待处理阶段 Pending

    • 处理完成后,切换到 Fulfilled 状态/ Rejected 状态

    • 根据状态,执行 then 方法/执行 catch 方法 内的回调函数

    then 方法返回新的 Promise,此时就支持链式调用

    这里创建一个 Promise 对象,Promise 内部维系着 resolve 和 reject 方法,resolve 会让 Promise 对象进入 Fulfilled 状态,并将 resolve 方法的第一个参数传给后续 then 所指定的 onFulfilled 函数。reject 方法同理,只不过是切换到 Rejected 状态,并将参数传给 catch 所指定的 onRejected 函数。

    一步步打造心中的 Promise

    基础实现

    先抛开 rejected,实现一个 Promise 的调用链的简单代码如下:

    function Promise (fn) {
        var deferreds = []
        this.then= function (onFulfilled){
            deferreds.push(onFulfilled) // 将 onFulfilled 函数压入 deferreds 队列中
        }
        function resolve (value) {
            deferreds.forEach(deferred => {
                deferred(value) // 执行deferreds 中的 onFulfilled 函数队列
        })
    }
        fn(resolve)
    }
    

    深入理解上面代码逻辑:

    • then 方法将 onFulfilled 函数压入 deferreds 队列中。

    • 将 resolve 传给创建 Promise 时传入的函数,resolve 的作用是将 deferreds 中的 onFulfilled 函数队列逐一执行。

    但是这段代码暴露出一个严重的问题,如果 Promise 执行的是同步代码,resolve 是早于 then 方法的执行,这样造成一个问题:then 还没有及时把 onFulfilled 函数压入队列,此时 deferreds 还是空数组,resolve 执行后,后续注册到 deferreds 数组内的 onFulfilled 函数将不再执行。

    这里我们可以把 deferreds 数组视为水桶,onFulfilled 视为饮用水,resolve 视为开关。then 操作就是将饮用水一点点地注入到水桶中。想想我们还没将水加到水桶中(执行 then 操作)就打开开关(执行 resolve),这肯定是接不到水的。

    解决的办法就是将 resolve 函数的执行改为异步。

    Promises/A+ 规范明确要求回调需要通过异步方式执行,保证一致可靠的执行顺序。通过 setTimeout 方法,我们可以轻松实现:

    function resolve (value) {
        setTimeout(()=> {
            pending.forEach(deferred => {
               deferred(value)
            })
        },0)
    }
    

    这样我们就可以把 resolve 执行放到下一个时钟周期。

    引入状态

    按照 Promise 规范,我们需要引入三种互斥的状态:pending、fulfilled、rejected。

    image.png
    执行 resolve 会将 pending 状态切换到 fulfilled,在此之后添加到 then 的函数都会立即被调用。

    现在我们的代码如下:

    function Promise(fn) {
        const deferreds= []
        var state ='pending'
        var value = null
        this.then = function (onFulfilled) {
            if (state==='pending') {
                deferreds.push(onFulfilled)
                return this
            }
            onFulfilled(value)
            return this
        }
        function resolve (_value) {
            state = 'fulfilled' // 状态切换到 fulfilled
            value = _value
            setTimeout(()=> {
                deferreds.forEach(deferred => {
                    deferred(value)
                })
            },0)
        }
        fn(resolve)
    }
    

    有了上面的基础,我们可以简单地调用 Promise:

    asyncreadfile('/README. md','utf-8').then(data => {
        console. log( data)
    })
    

    为了串行 Promise,我们在 then 中返回 this,并设置一个 value 来保存传给 resolve 的值。

    asyncreadfile('/README. md', 'utf-8').then(data => {
        console. log(data)
        return asyncreadfile('/package.json','utf-8')
    }). then(data => {
        console. log( data)
    })
    

    像上面这样调用,虽然可以通过,但是两次输出的 data 是相同的值,并不是真正意义上的链式调用。

    串行 Promise

    只要 then 方法每次调用都返回一个 Promise 对象,前一个 Promise 对象持有后一个 Promise 对象的 resolve 方法,这样串行就变得非常简单了。

    这里需要对 then 方法进行改造:

    this.then= function (onFulfilled) {
        return new Promise( resolve => { // 每次都返回一个Promise对象
            handle({ onFulfilled, resolve })
        })
    }
    function handle (deferred) {
        if (state==='pending') {
            deferreds.push(deferred)
            return
        }
        const ret =deferred.onFulfilled(value)
        deferred.resolve(ret)
    }
    

    这里完成的主要任务是:

    then 方法中返回一个新的 Promise 对象,这样每次执行 then 方法,都返回一个 Promise 对象,让链式调用成为可能。

    新创建的 Promise 对象调用上一级 Promise 的 handle 方法,传递自身的 resolve 方法和当前的 onFulfilled 函数。

    handle 相比之前的 then 多了一行 deferred.resolve(ret),这一步是链式调用的关键点。此刻的 resolve 是下一级 Promise 的方法,上一级 Promise 执行这段方法调用,就开启了链式调用。

    我们继续重构前面的 Promise 代码,这里主要修改的是 resolve 方法。

    function Promise (fn) {
        const deferreds = []
        var state = 'pending'
        var value = null
        this.then= function (onFulfilled) {
            // then方法永远会返回一个Promise对象
            return new Promise(resolve => {
                // handle为上一级Promise的方法
                handle({ onFulfilled, resolve })
            })
        }
        function handle (deferred) {
            if (state === 'pending') {
                // then方法将 deferred传入时,先压入到 deferreds中
                deferreds.push( deferred)
                return
            }
            // 执行 Bridge Promise前一个 Promise对象的 then 方法的onFulfilled函数
            const ret = deferred,.onFulfilled(value)
            // resolve执行 deferreds中的onFulfilled方法,即下一
            // 个 Bridge Promise的then中的回调函数
            deferred.resolve(ret)
        }
        function resolve(_value) {
            // 如果是Promise对象
            if (_value &&(typeof _value ==='object' || typeof
                          _value === 'function')) {
                const then = _value.then
                if (typeof then === 'function') {
                    // 将 resolve延迟到 promise执行完毕后调用,切换
                    Bridge Promise的状态
                    then.call(_value, resolve)
                    return
                }
            }
            // 如果是其它值
            state = 'fulfilled'
            value = _value
            setTimeout(()=> {
                deferreds.forEach(deferred => {
                    handle(deferred)
                })
            },0)
        }
        fn(resolve)
    }
    

    Promise 具体流程

    1. 实例化一个最初的 Promise 对象,设置最初的状态为 pending。

    2. 通过 then 方法,创建一个新的 Promise 对象,由于上一级 Promise 暂时处于 pending 状态,当前 then 方法的 onFulfilled 函数和新 Promise 的 resolve 方法放入到上一级 Promise 的 deferreds 数组中。

    3. 这样就形成这样一个画面:第一个 Promise 被实例化,调用 then 方法。then 会返回一个新的 Promise 对象,在上一个 then 方法的基础上继续通过新 Promise 的 then,形成一条调用链。 每一个被创建出来的新 Promise 的 resolve 都将传给上一级的 Promise 的 deferreds 数组来维护。

    4. 在第一个 Promise 对象的回调函数中执行异步操作,完成后调用 Promise 的 resolve 方法。

    5. resolve 允许传入一个参数,该参数的值通过 Promise 内部的 value 变量维护。resolve 会把 Promise 的状态修改为 fulfilled,然后异步调用 handle 依次处理 deferreds 数组中的每一个 deferred。

    6. 此时第一个 Promise 的状态在上一步骤中被改为 fulfilled,于是 handle 主要完成的工作是,执行 deferred 的 onFulfilled 函数,并调用下一个 Promise 的 resolve 方法。

    7. 下一个 Promise 的 resovle 在上一级被执行成功后,同样会将状态切换到 fulfilled ,重复步骤 6 直到结束。

    相关文章

      网友评论

          本文标题:Promise内部实现原理(个人笔记)

          本文链接:https://www.haomeiwen.com/subject/vnxocktx.html