從零開始寫一個 Promise 庫

原文:
Write Your Own Node.js Promise Library from Scratch

作者:code_barbarian

Promise 已是 JavaScript 中異步處置懲罰的基石,回調的場景將會越來越少,而且如今可以直接在 Node.js 運用 async/await。async/await 基於 Promise,因而須要相識 Promise 來控制 async/await。這篇文章,將引見怎樣編寫一個 Promise 庫,並演示怎樣運用 async/await。

Promise 是什麼?

在 ES6 範例中,Promise 是一個類,它的組織函數接收一個 executor 函數。Promise 類的實例有一個 then() 要領。依據範例,Promise 另有其他的一些屬性,但在這裏可以臨時疏忽,因為我們要完成的是一個精簡版的庫。下面是一個 MyPromise 類的腳手架:

class MyPromise {
    // `executor` 函數接收兩個參數,`resolve()` 和 `reject()`
    // 擔任在異步操縱勝利(resolved)或許失利(rejected)的時刻挪用 `resolve()` 或許 `reject()`
    constructor(executor) {}

    // 當 promise 的狀況是 fulfilled(完成)時挪用 `onFulfilled` 要領,
    // 當 promise 的狀況是 rejected(失利)時挪用 `onRejected` 要領
    // 到目前為止,可以以為 'fulfilled' 和 'resolved' 是一樣的
    then(onFulfilled, onRejected) {}
}

executor 函數須要兩個參數,resolve()reject()。promise 是一個狀況機,包括三個狀況:

  • pending:初始狀況,既不是勝利,也不是失利狀況
  • fulfilled:意味着操縱勝利完成,返回效果值
  • rejected:意味着操縱失利,返回毛病信息

如許很輕易就可以完成 MyPromise 組織函數的初始版本:

constructor(executor) {
    if (typeof executor !== 'function') {
        throw new Error('Executor must be a function')
    }

    // 初始狀況,$state 示意 promise 的當前狀況
    // $chained 是當 promise 處在 settled 狀況時須要挪用的函數數組
    this.$state = 'PENDING'
    this.$chained = []

    // 為處置懲罰器函數完成 `resolve()` 和 `reject()`
    const resolve = res => {
        // 只需當 `resolve()` 或 `reject()` 被挪用
        // 這個 promise 對象就不再處於 pending 狀況,被稱為 settled 狀況
        // 挪用 `resolve()` 或 `reject()` 兩次,以及在 `resolve()` 以後挪用 `reject()` 是無效的
        if (this.$state !== 'PENDING') {
            return
        }
        // 背面將會談到 fulfilled 和 resolved 之間存在細微差別
        this.$state = 'FULFILLED'
        this.$internalValue = res
        // If somebody called `.then()` while this promise was pending, need
        // to call their `onFulfilled()` function
        for (const { onFulfilled } of this.$chained) {
            onFulfilled(res)
        }
    }
    const reject = err => {
        if (this.$state !== 'PENDING') {
            return
        }
        this.$state = 'REJECTED'
        this.$internalValue = err
        for (const { onRejected } of this.$chained) {
            onRejected(err)
        }
    }

    // 如範例所言,挪用處置懲罰器函數中的 `resolve()` 和 `reject()`
    try {
        // 假如處置懲罰器函數拋出一個同步毛病,我們以為這是一個失利狀況
        // 須要注重的是,`resolve()` 和 `reject()` 只能被挪用一次
        executor(resolve, reject)
    } catch (err) {
        reject(err)
    }
}

then() 函數的完成更簡樸,它接收兩個參數,onFulfilled()onRejected()then() 函數必需確保 promise 在 fulfilled 時挪用 onFulfilled(),在 rejected 時挪用 onRejected()。假如 promise 已 resolved 或 rejected,then() 函數會馬上挪用 onFulfilled()onRejected()。假如 promise 仍處於 pending 狀況,就將函數推入 $chained 數組,因而後續 resolve()reject() 函數依然可以挪用它們。

then(onFulfilled, onRejected) {
    if (this.$state === 'FULFILLED') {
        onFulfilled(this.$internalValue)
    } else if (this.$state === 'REJECTED') {
        onRejected(this.$internalValue)
    } else {
        this.$chained.push({ onFulfilled, onRejected })
    }
}

*除此之外:ES6 範例示意,假如在已 resolved 或 rejected 的 promise 挪用 .then(), 那末 onFulfilled()onRejected() 將在下一個時序被挪用。因為本文代碼只是一個教授教養示例而不是範例的準確完成,因而完成會疏忽這些細節。

Promise 挪用鏈

上面的例子特地疏忽了 promise 中最龐雜也是最有效的部份:鏈式挪用。假如 onFulfilled() 或許 onRejected() 函數返回一個 promise,則 then() 應當返回一個 “locked in” 的新 promise 以婚配這個 promise 的狀況。比方:

p = new MyPromise(resolve => {
    setTimeout(() => resolve('World'), 100)
})

p
    .then(res => new MyPromise(resolve => resolve(`Hello, ${res}`)))
    // 在 100 ms 后打印 'Hello, World'
    .then(res => console.log(res))

下面是可以返回 promise 的 .then() 函數完成,如許就可以夠舉行鏈式挪用。

then(onFulfilled, onRejected) {
    return new MyPromise((resolve, reject) => {
        // 確保在 `onFulfilled()` 和 `onRejected()` 的毛病將致使返回的 promise 失利(reject)
        const _onFulfilled = res => {
            try {
                // 假如 `onFulfilled()` 返回一個 promise, 確保 `resolve()` 能正確處置懲罰
                resolve(onFulfilled(res))
            } catch (err) {
                reject(err)
            }
        }
        const _onRejected = err => {
            try {
                reject(onRejected(err))
            } catch (_err) {
                reject(_err)
            }
        }
        if (this.$state === 'FULFILLED') {
            _onFulfilled(this.$internalValue)
        } else if (this.$state === 'REJECTED') {
            _onRejected(this.$internalValue)
        } else {
            this.$chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected })
        }
    })
}

如今 then() 返回一個 promise,然則還須要完成一些事情:假如 onFulfilled() 返回一個 promise,resolve() 要可以正確處置懲罰。所以 resolve() 函數須要在 then() 遞歸挪用,下面是更新后的 resolve() 函數:

const resolve = res => {
    // 只需當 `resolve()` 或 `reject()` 被挪用
    // 這個 promise 對象就不再處於 pending 狀況,被稱為 settled 狀況
    // 挪用 `resolve()` 或 `reject()` 兩次,以及在 `resolve()` 以後挪用 `reject()` 是無效的
    if (this.$state !== 'PENDING') {
        return
    }

    // 假如 `res` 是 thenable(帶有then要領的對象)
    // 將鎖定 promise 來堅持跟 thenable 的狀況一致
    if (res !== null && typeof res.then === 'function') {
        // 在這類情況下,這個 promise 是 resolved,然則仍處於 'PENDING' 狀況
        // 這就是 ES6 範例中說的"一個 resolved 的 promise",能夠處在 pending, fulfilled 或許 rejected 狀況
        // http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects
        return res.then(resolve, reject)
    }

    this.$state = 'FULFILLED'
    this.$internalValue = res
    // If somebody called `.then()` while this promise was pending, need
    // to call their `onFulfilled()` function
    for (const { onFulfilled } of this.$chained) {
        onFulfilled(res)
    }

    return res
}

為了簡樸起見,上面的例子省略了一旦 promise 被鎖定用以婚配另一個 promise 時,挪用 resolve() 或許 reject() 是無效的癥結細節。在上面的例子中,你可以 resolve() 一個 pending 的 promise ,然後拋出一個毛病,然後 res.then(resolve, reject) 將會無效。這僅僅是一個例子,而不是 ES6 promise 範例的完整完成。

上面的代碼說清楚明了 resolved 的 promise 和 fulfilled 的 promise 之間的區分。這類區分是玄妙的,而且與 promise 鏈式挪用有關。resolved 不是一種真正的 promise 狀況,但它是ES6範例中定義術語。當對一個已 resolved 的 promise 挪用 resolve(),能夠會發作以下兩件事之一:

  • 在挪用 resolve(v)時,假如 v 不是一個 promise ,那末 promise 馬上成為 fulfilled。在這類簡樸的情況下,resolved 和 fulfilled 就是一樣的。
  • 在挪用 resolve(v)時,假如 v 是另一個 promise,那末這個 promise 一向處於 pending 直到 v 挪用 resolve 或許 reject。在這類情況下, promise 是 resolved 但處於 pending 狀況。

與 Async/Await 一同運用

癥結字 await 會停息實行一個 async 函數,直到守候的 promise 變成 settled 狀況。如今我們已有了一個簡樸的克己 promise 庫,看看連繫運用 async/await 中時會發作什麼。向 then() 函數增加一個 console.log() 語句:

then(onFulfilled, onRejected) {
    console.log('Then', onFulfilled, onRejected, new Error().stack)
    return new MyPromise((resolve, reject) => {
        /* ... */
    })
}

如今,我們來 await 一個 MyPromise 的實例,看看會發作什麼。

run().catch(error => console.error(error.stack))

async function run() {
    const start = Date.now()
    await new MyPromise(resolve => setTimeout(() => resolve(), 100))
    console.log('Elapsed time', Date.now() - start)
}

注重上面的 .catch() 挪用。catch() 函數是 ES6 promise 範例的中心部份。本文不會細緻報告它,因為 .catch(f) 相當於 .then(null, f),沒有什麼迥殊的內容。

以下是輸出內容,注重 await 隱式挪用 .then() 中的 onFulfilled()onRejected() 函數,這是 V8 底層的 C++ 代碼(native code)。另外,await 會一向守候挪用 .then() 直到下一個時序。

Then function () { [native code] } function () { [native code] } Error
    at MyPromise.then (/home/val/test/promise.js:63:50)
    at process._tickCallback (internal/process/next_tick.js:188:7)
    at Function.Module.runMain (module.js:686:11)
    at startup (bootstrap_node.js:187:16)
    at bootstrap_node.js:608:3
Elapsed time 102

更多

async/await 是異常壯大的特徵,但控制起來輕微有點難題,因為須要運用者相識 promise 的基本原則。 promise 有許多細節,比方捕捉處置懲罰器函數中的同步毛病,以及 promise 一旦處理就沒法轉變狀況,這使得 async/await 成為能夠。一旦對 promise 有了充足的明白,async/await 就會變得輕易很多。

    原文作者:離獨逸
    原文地址: https://segmentfault.com/a/1190000014391336
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞