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

2020-07-13  本文已影响0人  kevision

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

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

三种生命状态

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

image.png

工作原理

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


image.png

Promise 大致的工作流程是

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)
}

深入理解上面代码逻辑:

但是这段代码暴露出一个严重的问题,如果 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 直到结束。

上一篇下一篇

猜你喜欢

热点阅读