在使用 Promise 的時候,我們最簡單的理解與用法就是像上面的程式碼一樣,把非同步結果提供給 resolve 作參數,然後透過給 then 方法傳遞一個自訂函數作為結果處理函數。但 resolve 和 reject 這兩個參數到底是什麼?在這背後,它的基本運作方式到底是怎麼樣的呢?讓我們從規範的角度來初步了解它。
new Promise((resolve, reject) => setTimeout(resolve, 1000, 'foo')) .then(console.log) // foo (1s后)
TL;DR
1、promise 的工作機制與callback 類似,都採用內部的抽像操作Job 來實作非同步
2、Promise 建構函式裡的resolve /reject 函數是內部建立的,在呼叫它們時傳入的參數就是要解析的結果,把它和promise 已經儲存的使用者傳入的處理函數一起插入到Job 佇列中。傳入的參數也可以是一個 promise,在 Promise.all/race 的內部就有用到。
3、Promise.prototype.then 根據目前的promise 的狀態來決定是立即將promise 中儲存的結果取出並和參數中的處理函數一起直接插入到Job 佇列中還是先與promise 關聯起來作為結果處理函數。 then 會隱式呼叫 Promise 建構函數建構新的 promise 並傳回。
4、Promise.all 先建立一個新的promise,然後先、初始化一個空的結果數組和一個計數器來對已經resolve 的promise進行計數,之後會進行迭代,對於每個迭代值它都會為其創造一個promise,並設定這個promise的then為向結果數組裡添加結果以及計數器--,當計數器減至0時就會resolve最終結果。
5、Promise.race 也是會創造一個新的主promise,之後主要是根據promise 只能resolve 一次的限制,對於每個迭代值都會創造另一個promise,先resolve的也會先被主promise resolve 回傳結果。
new Promise(executor)
首先從Promise 這個建構子說起,它是全域物件的Promise 屬性的值,這也就是為什麼在瀏覽器環境下我們能直接呼叫它的原因,就像String, Array 這些建構子一樣。
new Promise(executor)的第一步就像其他建構子一樣,按照Promise 的prototype 來建構一個新對象,並初始化了幾個內部插槽[[PromiseState]],[[PromiseResult] ],[[PromiseFullfillReactions]],[[PromiseRejectReactions]],[[PromiseIsHandled]]來記錄一些相關的信息,可以從名字來大致推斷出他們的作用,詳情我們下文再提。這裡它們的初始值除了[[PromiseResult]]依序為 "pending",空 list,空 list,false。
下一步,ES 會根據這個 promise 物件來產生用來resolve promise的 resolve function 和用來 reject promise 的 reject function。然後呼叫 executor,以 resolve function 和 reject 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。
那麼 resolve function 和 reject 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, <
再來看 resolve function,與 reject function 一樣,在產生它時,會把它與某個 promise 關聯起來。在執行時,我們傳入的參數叫做 resolution。如果 promise 已經 resolve 過,就回傳 undefined。之後的情況就相對複雜一些了。
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, <
在說明 EnqueueJob 之前,先來看看 Job 是個什麼東西。簡單來說,它就像是回調的內部實現機制:「當沒有其他 ES 在運行時,初始化並執行自己對應的 ES。」。我們有一個待執行的 FIFO 的 Job 佇列,以及目前的執行環境 running execution context 和 execution context stack,當後兩者均為空時,才會執行 Job 佇列的第一個。
ES 規定實作裡至少要有兩個 Job 佇列,ScriptJobs 和 PromiseJobs。當我們呼叫 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 function 和reject 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, <
Promise.resolve(x)
#像then 那樣建立一個promiseCapability,然後直接呼叫其中的resolve function 並傳入要解析的值x,最後傳回其中的新promise.
Promise.all(iterable)
#Promise.all也會像then 那樣建立一個promiseCapability,裡麵包含一個新的promise 及其關聯的resolve function 和reject function,之後就結合迭代器循環:1.如果迭代完了並且計數器為0則調用promiseCapability 的resolve 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,感興趣的話可以繼續了解一下。
以上是深入理解JS中promise的詳細內容。更多資訊請關注PHP中文網其他相關文章!