深刻 Promise

> new Promise((resolve, reject) => setTimeout(resolve, 1000, 'foo'))  
> .then(console.log)  
> // foo (1s後)

在使用 Promise 的時候,咱們最簡單的理解與用法就是像上面的代碼那樣,把異步結果提供給 resolve 做參數,而後經過給 then 方法傳遞一個自定義函數做爲結果處理函數。但 resolve 和 reject 這兩個參數究竟是什麼?在這背後,它的基本工做方式究竟是怎樣的呢?讓咱們從規範的角度來初步瞭解它吧。 javascript

參考: ES8 Promise前端

TL;DR

  • promise 的工做機制與 callback 相似,都採用內部的抽象操做 Job 來實現異步
  • Promise 構造函數裏的 resolve/reject 函數是內部建立的,在調用它們時傳入的參數就是要解析的結果,把它和 promise 已經存儲的用戶傳入的處理函數一塊兒插入到 Job 隊列中。傳入的參數也能夠是一個 promise,在 Promise.all/race 的內部就有用到。
  • Promise.prototype.then 根據當前的 promise 的狀態來決定是當即將 promise 中存儲的結果取出並和參數中的處理函數一塊兒直接插入到 Job 隊列中仍是先與 promise 關聯起來做爲結果處理函數。then 會隱式調用 Promise 構建函數構建新的 promise 並返回。
  • Promise.all 先建立一個新的 promise,而後先、初始化一個空的結果數組和一個計數器來對已經 resolve 的 promise進行計數,以後會進行迭代,對於每一個迭代值它都會爲其創造一個promise,並設定這個promise的then爲向結果數組裏添加結果以及計數器--,當計數器減至0時就會resolve最終結果。
  • Promise.race 也是會建立一個新的主 promise,以後主要是根據 promise 只能 resolve 一次的限制,對於每一個迭代值都會創造另外一個promise,先resolve的也就會先被主 promise resolve 返回結果。

new Promise(executor)

首先從 Promise 這個構造函數提及,它是全局對象的 Promise 屬性的值,這也就是爲何瀏覽器環境下咱們能直接調用它的緣由,就像 String, Array 這些構造函數同樣。 java

new Promise(executor)的第一步就像其餘構造函數同樣,按照 Promise 的 prototype 來構建一個新對象,並初始化了幾個內部插槽[[PromiseState]][[PromiseResult]][[PromiseFullfillReactions]][[PromiseRejectReactions]][[PromiseIsHandled]]來記錄一些相關的信息,能夠從名字來大體推斷出他們的做用,詳情咱們下文再提。這裏它們的初始值除了[[PromiseResult]]依次爲 "pending",空 list,空 list,false。 react

下一步,ES 會根據這個 promise 對象來生成用來resolve promise的 resolve function 和用來 reject promise 的 reject function。而後調用 executor,以 resolve functionreject function 爲參數,若是在這個過程當中出錯了,就直接 reject promise。最後返回 promise。 數組

那什麼又是 resolve,什麼又是 reject 呢。咱們知道 Promise 的狀態,也就是[[PromiseState]]有三種值: pending, fullfilled, rejected,用 reject function 就能夠 reject promise,把它的狀態從 pending 變爲rejected。不過 resolve function 既能夠 fullfill promise 來把promise的狀態從 pending 變爲 fullfilled,也能夠用來 reject promise。 promise

那麼 resolve functionreject function 到底作了些什麼呢? 瀏覽器

先來看 reject function ,首先在生成它的時候,會給它初始化[[Promise]][[AlreadyResolved]]插槽,也就是把它和某個 promise 關聯起來。在執行時,會傳入一個參數 reason,並只有當[[AlreadyResolved]]是 false,也就是還沒 resolve 過、狀態爲 pending 時,纔會調用返回 RejectPromise、傳入 promise 和 reason 參數來 reject promise,不然返回 undefined。
RejectPromise(promise, reason),除了把[[PromiseState]]從 pending 變爲 rejected 以外,還會把 promise 的結果[[PromiseResult]]的值設爲 reason,並會取出 promise 的[[PromiseRejectReactions]]中已存的記錄(相信讀者們已經明白後面還會有一個操做來向這個內部插槽裏存記錄),並用 TriggerPromiseReactions 調用這些記錄作後續處理,並傳入 reject 的緣由 reason。相似的,resolve function 中用到的 FullfillPromise(promise, value) 操做把 promise 的狀態變爲 fulfilled,抽取[[PromiseFullfillReactions]]的值調用 TriggerPromiseReactions,並傳入 fulfilled 的結果 value。 微信

TriggerPromiseReactions(reactions, argument) 會調用 EnqueueJob("PromiseJobs", PromiseReactionJob, <<reactions, argument>>),待會再詳細說明。 異步

再來看 resolve function,與 reject function 同樣,在生成它時,會把它與某個 promise 關聯起來。在執行時,咱們傳入的參數叫作 resolution。若是 promise 已經 resolve 過,就返回 undefined。以後的狀況就相對複雜一些了。async

  1. 若是用戶把這個 promise 自己傳給了 resolve function 做爲參數 resolution,就會建立一個 TypeError,throw 它,並調用 RejectPromise,reason 參數爲這個 TypeError。
  2. 若是 resolution 的類型不是 Object,就調用 FulfillPromise(promise, resolution)
  3. 其他的狀況就是 resolution 是除了自身之外的帶 then 的對象 (Promise) 的狀況了。

    • 若是 resolution 是個不帶then的對象,就 RejectPromise
    • 若是有 then 屬性但不能調用,也 FulfillPromise, 。
    • 若是有 then 屬性而且能夠調用,就 EnqueueJob("PromiseJobs", PromiseResolveThenableJob, <<promise, resolution, thenAction>>)

在說明 EnqueueJob 以前,先來看看 Job 是個什麼東西。簡單來講,它就像是回調的內部實現機制:「當沒有其餘 ES 在跑時,初始化並執行本身對應的 ES。「。咱們有一個待執行的 FIFO 的 Job 隊列,以及當前的執行環境 running execution context 和 execution context stack,當後二者均爲空時,纔會執行 Job 隊列的第一個。

ES 規定實現裏至少要有兩個 Job 隊列,ScriptJobsPromiseJobs。當咱們調用 EnqueueJob("PromiseJobs", ...)時,也就將要完成的 Job 和它們的參數插入到了 PromiseJobs 這個隊列。能夠看到,Promise 下有兩種 Job

  1. PromiseReactionJob(reaction, argument)
    reaction 有三個內部插槽 [[Capability]][[Type]][[Handler]],分別表示 [[關聯的 promise 及相關的resolve function 和 reject function]][[類別]][[handler]]。若是用戶沒有給 handler(undefined),就根據類別是 Fulfill 仍是 Reject 來把 argument 看成結果。若是給了 handler,就用它來對 argument 進行進一步處理。最後根據這個結果來用 resolve function 和 reject function 進行處理並返回。
  2. PromiseResolveThenableJob(promiseToResolve, thenable, then)
    建立和 promiseToResolve 關聯的 resolve function 和 reject function。以 then 爲調用函數,thenable 爲this,resolve function和reject function 爲參數調用返回。

Promise.prototype.then(onfulfilled, onrejected)

首先是建立一個 promiseCapability,它包含了一個新的 promise 和相關聯的 resolve functionreject function。promise 的產生就是像正常使用 Promise 構造函數那樣構建一個 promise,不過傳給構造函數 executor 是內部自動建立的,做用是把 resolve/reject function 記錄到PromiseCapability中。
根據 promiseCapability 和 onfulfilled/onrejected 建立兩個分別用於 fulfill 和 reject 的PromiseReaction,也就是 PromiseJobs 裏最終要執行的操做。
若是當前的 promise(this)是 pending 狀態,就把這兩個 reaction 分別插入到 promise的[[PromiseFulfillReactions]][[PromiseRejectReactions]]隊列中。但若是此時 promise 已是 fulfilled 或是 rejected 狀態了,就從 promise 的[[PromiseResult]]取出值 result,做爲 fulfilled 的結果/reject 的緣由,插入到 Job 隊列裏,EnqueueJob("PromiseJobs", PromiseReactionJob, <<reaciton, result>>),最後返回 prjomiseCapability 裏存儲的新 promise。Promise.prototype.catch(onrejected) 就是 Promise.prototype.then(undefined, onrejected)

Promise.resolve(x)

像 then 那樣建立一個 promiseCapability,而後直接調用其中的 resolve function 並傳入要解析的值x,最後返回其中的新 promise.

Promise.all(iterable)

Promise.all也會像 then 那樣建立一個 promiseCapability,裏面包含着一個新的 promise 及其關聯的 resolve functionreject function,以後就結合迭代器循環:

  1. 若是迭代完了而且計數器爲0則調用 promiseCapabilityresolve function 來 resolve 結果數組
  2. 不然計數器加1,而後取出下一個迭代的值,傳給 Promise.resolve 也構建一個新的 promise,而後內部建立一個 Promise.all Resolve Element Function,傳給這個新 promise 的 then 用來把結果添加到結果數組並使計數器減一。

Promise.race(iterable)

一樣的,建立一個 promiseCapability,而後進行迭代,用 Promise.resolve 來構建一個新的 promise,以後調用這個新 promise 的 then 方法,傳入 promiseCapability 裏的 resolve/reject function,結合以前提到的 promise 只會 resolve 一次,能夠看到確實頗有 race 的意味。

結語

看到這裏,不知道你們是否對 Promise 有了更深的理解了呢。再往深一步,ES6裏新提出的 async/await 實際上也是應用了 Generator 的思想與 Promise,感興趣的話能夠繼續瞭解一下。


文 / Kacxxia

並無做者介紹

本文已由做者受權發佈,版權屬於創宇前端。歡迎註明出處轉載本文。本文連接:https://knownsec-fed.com/2018-08-22-shen-ru-promise/

想要看到更多來自知道創宇開發一線的分享,請搜索關注咱們的微信公衆號:創宇前端(KnownsecFED)。

歡迎點贊、收藏、留言評論、轉發分享和打賞支持咱們。打賞將被徹底轉交給文章做者。

感謝您的閱讀。

相關文章
相關標籤/搜索