Promise原理分析

weixin_34321977發表於2019-01-14

前言

最近討論到了 Promise,此前知道也使用過它,但是對於其原理卻不甚瞭解。

於是翻了翻 MDN 上的文件,又找了幾篇文章看了看,研究了研究。

最終,自己嘗試了一番,對於其原理也有所瞭解。

Promise的使用

先回顧一下 Promise 的使用。

這裡只是簡單的呼叫,如果需要系統學習,還是移步 MDN 上的文件。

new Promise((resolve) => {
    setTimeout(() => {
        console.log(1)
        resolve(1)
    })
})
.then((response) => (console.log(++response), response))
.then((response) => {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log(1)
            resolve(1)
        })
    })
})
.then((response) => (console.log(++response), response))

執行結果如下:

6268204-27e0c2971864a321.jpg
img

程式碼分析

一個 Promise 就是一個代表了非同步操作最終完成或者失敗的物件。

---- 摘自 MDN

第一步,例項化一個 Promise 物件。

它的引數就是一個函式,此函式會接受兩個引數,分別是 resolvereject

當操作成功時,則執行 resolve

當操作失敗時,則執行 reject

new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(1)
        resolve(1)
    })
})

回撥之後的操作可以用其提供的 then 方法。

then 接受兩個引數,分別是 resolvereject。原理同上。

new Promise((resolve) => {
    setTimeout(() => {
        console.log(1)
        resolve(1)
    })
})
.then((response) => (console.log(++response), response))

從上述程式碼裡可以看出,我們使用了 setTimeout

也就是說,then 方法會在 setTimeout 裡的方法之前執行。

如果沒有 Promise 的特殊機制,輸出的結果可能就是 undefined、undefined、1、1 了。

這就是 Promise 的魅力,也是我想要知道的地方。

原理分析

既然是要分析其原理,那麼按照其特性和使用方式來仿造一個是一個不錯的選擇。(主要是我不知道它的原始碼是什麼<(_ _)>)

基本結構

先實現例項化的部分。

從上面的程式碼,我們已經知道,例項化的時候,只需要傳 一個引數 就可以了,同時這個 引數 是一個 函式

從執行結果來看,在我們例項化的時候,這個 函式 已經 執行 了。

並且這個函式會接收到 兩個引數,這 兩個引數 都是 函式

function PromiseDemo(foo) {
    if (typeof foo != 'function') {
        throw Error('Promise 的引數必須是函式')
    }

    // 成功後執行的函式
    let resolve = response => {}

    // 失敗後執行的函式
    let reject = response => {}

    // 例項化後,立即執行函式
    foo(resolve, reject)
}

then 方法部分。

then 方法是 Promise 物件的一個方法,我們需要將其暴露出來。

同時,then 方法支援鏈式的呼叫,所以返回值肯定是一個 Promise 物件。

function PromiseDemo(foo) {
    // 成功後執行的函式
    let resolve = response => {}

    // 失敗後執行的函式
    let reject = response => {}

    // 實現 then 函式
    let then = (onResolved, onRejected) => {
        return new PromiseDemo((resolved, rejected) => {
            // todo...
        })
    }

    // 例項化後,立即執行函式
    foo(resolve, reject)

    return {
        then: then
    }
}

到這裡,基本的結構已經有了,我們已經可以執行相關程式碼了,就是還沒有想要的效果。

屬性完善

通過一開始的示例分析,我們可以看出,then 引數接收到的引數是從上層傳遞到下層的

這句話有點繞,引數的引數。所以,Promise 物件就要有一個存放返回值的屬性,這裡就給它命名為 data

同時,then 函式的引數,是在 setTimeout 執行之後才執行的。那麼,Promise 物件就要有一個存放任務的列表,這裡就給它命名為 queue

到這裡,問題就來了,什麼時候應該將任務放入任務列表呢?是還沒有執行的,還是執行成功的,還是執行失敗的?

說到這裡,我們就要有一個標識表明這個任務是處於什麼狀態,是未執行,還是已執行,還是執行失敗。這裡就給它命名為 state,值分別為 pendingresolvedrejected

上述文字有點長,主要就是定義了 Promise 的三個屬性:statequeuedata

let state = 'pending'
    , queue = []
    , data

實現 then 函式

為什麼不返回 this

上面分析例項的時候有說到,為了能夠達成鏈式呼叫,我們在 then 函式的返回值要是一個 Promise 物件。

而且基礎程式碼裡編寫的是直接例項化了一個新的物件,那麼可能會造成疑惑,直接返回 this 不行麼?

答案是否定的,原因很簡單,是由於 Promise 自身的屬性決定了不能直接返回 this,不然其中任何一個屬性發生修改都會對後面的操作造成影響。

編碼

迴歸正軌。

let then = (onResolved, onRejected) => {
    return new PromiseDemo((resolved, rejected) => {
        // todo...
    })
}

then 函式有兩個引數,並且返回值是一個例項化的 Promise 物件。

也就是說,在處理的過程中,我們將會涉及四個函式,分別是:onResolved, onRejected, resolved, rejected

先分析一下四個函式的執行情況。

如果之前的 Promise 尚未處理(使用的非同步程式碼,比如:setTimeout, ajax ),那麼當前的狀態 state = 'pending',那我們就不能執行任何一個函式。

這個時候我們就要將這四個函式儲存好,表明這屬於同一個任務。

// 如果還沒執行結束,則將任務推向佇列,並返回
if (state == 'pending') {
    queue.push([onResolved, onRejected, resolved, rejected])
    return
}

如果之前的 Promise 已經執行,那麼當前的狀態 state = 'resolved' 或者 state = 'rejected'

這個時候我們就要分辨到底是什麼情況了。

// callback 是 then 函式兩個引數中的一個
// next 是 Promise 成功失敗函式中的一個
let callback, next
// 判斷是否出錯
if (state == 'resolved') {
    callback = onResolved
    next = resolved
} else {
    callback = onRejected
    next = rejected
}

當我們處理完之後,我們就要執行相關函式了。

callback(data)

為了能夠將引數傳遞給下游,callback 的返回值需要直接給 next 使用。

next(callback(data))

好了,結束。

真的結束了麼?

由於 callbak 是使用者呼叫 then 函式傳遞過來的,那使用者真的一定會傳引數麼?使用者傳過來的引數一定是一個函式麼?那麼直接執行 callback 就會有問題!!!

// 如果是函式,則直接執行
// 並將引數傳遞給下游
if (typeof callback == 'function') {
    next(callback(data))
    return
}
next(data)

好了,這樣貌似沒什麼問題了,整理一下程式碼。

function then(onResolved, onRejected) {
    return new PromiseDemo((resolved, rejected) => {
        handle(onResolved, onRejected, resolved, rejected)
    })
}

function handle(onResolved, onRejected, resolved, rejected) {
    // 如果還沒執行結束,則將任務推向佇列,並返回
    if (state == 'pending') {
        queue.push([onResolved, onRejected, resolved, rejected])
        return
    }

    let callback, next
    // 判斷是否出錯
    if (state == 'resolved') {
        callback = onResolved
        next = resolved
    } else {
        callback = onRejected
        next = rejected
    }
    // 如果是函式,則直接執行
    // 並將引數傳遞給下游
    if (typeof callback == 'function') {
        next(callback(data))
        return
    }
    next(data)
}

實現 resolve 函式

相對於 then 函式的複雜,resolve 函式就相對簡單點。

為了避免重複執行,這裡直接將 state 置為 rejected

先考慮一下,如果傳過來的值是一個 Promise 物件,那我們該怎麼處理?如果是一個非 Promise 物件的引數,該怎麼處理?

由於 Promise 物件本身是擁有 then 函式的,我們應該執行它,這樣這個物件處理後的值,會流轉到下一層。

let resolve = response => {
    state = 'resolved'
    if (response && (typeof response == 'function' || typeof response == 'object')) {
        if (response.hasOwnProperty('then') && typeof response.then == 'function') {
            response.then.call(response, resolve, reject)
            return
        }
    }
    data = response
    runCallbacks()
}

這裡有個 runCallbacks 是幹嘛的呢?

還記得我們將未執行的任務放到 queue 中了不?那麼當前的任務執行完,是不是該執行下一個任務了呢?

let runCallbacks = () => {
    queue.forEach(callback => handle.apply(null, callback))
}

OK,到這裡,resolve 函式已經編寫完成了。

實現其它

Promise 還有其它的一些方法,例如:rejectcatch 等。

這裡就不一一寫了,下面該試驗一下我們的 PromiseDemo 是否有用了。

demo 測試

先來原始碼

function PromiseDemo(foo) {
    if (typeof foo != 'function') {
        throw Error('Promise 的引數必須是函式')
    }

    let state = 'pending'
        , queue = []
        , data

    function then(onResolved, onRejected) {
        return new PromiseDemo((resolved, rejected) => {
            handle(onResolved, onRejected, resolved, rejected)
        })
    }

    function handle(onResolved, onRejected, resolved, rejected) {
        // 如果還沒執行結束,則將任務推向佇列,並返回
        if (state == 'pending') {
            queue.push([onResolved, onRejected, resolved, rejected])
            return
        }

        let callback, next
        // 判斷是否出錯
        if (state == 'resolved') {
            callback = onResolved
            next = resolved
        } else {
            callback = onRejected
            next = rejected
        }
        // 如果是函式,則直接執行
        // 並將引數傳遞給下游
        if (typeof callback == 'function') {
            next(callback(data))
            return
        }
        next(data)
    }

    let runCallbacks = () => {
        queue.forEach(callback => handle.apply(null, callback))
    }

    let reject = response => {
        state = 'rejected'
        data = response
        runCallbacks()
    }

    let resolve = response => {
        state = 'resolved'
        if (response && (typeof response == 'function' || typeof response == 'object')) {
            if (response.hasOwnProperty('then') && typeof response.then == 'function') {
                response.then.call(response, resolve, reject)
                return
            }
        }
        data = response
        runCallbacks()
    }

    try {
        foo(resolve, reject)
    } catch (e) {
        reject(e)
    }

    return {
        then: then
    }
}

執行一開始的 demo,結果如下:

6268204-296a15da468d378a.jpg
img

流程圖

說也說了,寫也寫了,整個流程是什麼樣子的呢?

6268204-bfe5134c7de670bb.jpg
img

參考資料

  1. Promise原理解析

  2. 深入理解 Promise

最後

終於寫完了,有點囉嗦,基本上把我想表達的都表達出來了。

-- EOF --

本文轉載自IMJCW

原文連結:Promise原理分析

相關文章