為什麼?
了解 JavaScript Promises 如何在背景非同步執行回呼。
讓我們用 JavaScript 創建自己的 Promise!我們將遵循 Promise/A 規範,該規範概述了 Promise 如何處理非同步操作、解析、拒絕以及確保可預測的連結和錯誤處理。
為了簡單起見,我們將重點放在 Promises/A 規範中 ✅ 標記的關鍵規則。這不是一個完整的實現,而是一個簡化版本。這是我們將要建構的:
1.1 'promise' 是一個帶有 then 方法的物件或函數,其行為符合此規範。
1.2 thenable'是一個定義了then方法的物件或函數。
1.3 'value' 是任何合法的 JavaScript 值(包括未定義的、thenable 或一個promise)。
1.4 「異常」是使用 throw 語句拋出的值。
1.5 'reason' 是一個值,表示承諾被拒絕的原因。
承諾必須處於以下三種狀態之一:待定、已履行或已拒絕。
2.1.1。待處理時,承諾:✅
⟶ 可能會轉換為已完成或已拒絕狀態。
2.1.2。兌現後,承諾:✅
⟶不得轉換到任何其他狀態。
⟶ 必須有一個值,且不得更改。
2.1.3。當被拒絕時,一個承諾:✅
⟶不得轉換到任何其他狀態。
⟶必須有一個理由,而這個理由不能改變。
promise 必須提供 then 方法來存取其當前或最終的值或原因。
promise 的 then 方法接受兩個參數:
promise.then(onFulfilled, onRejected);
2.2.1。 onFulfilled 和 onRejected 都是可選參數:✅
⟶ 如果 onFulfilled 不是函數,則必須忽略它。
⟶ 如果 onRejected 不是函數,則必須忽略它。
2.2.2。如果 onFulfilled 是函數: ✅
⟶ 它必須在 Promise 完成後調用,並以 Promise 的值作為第一個參數。
⟶ 在承諾完成之前不得呼叫它。
⟶ 不得多次呼叫。
2.2.3。如果 onRejected 是函數,✅
⟶ 它必須在 Promise 被拒絕後調用,並以 Promise 的原因作為第一個參數。
⟶ 在承諾被拒絕之前不得調用它。
⟶ 不得多次呼叫。
2.2.4。在執行上下文堆疊僅包含平台程式碼之前,不得呼叫 onFulfilled 或 onRejected。 ✅
2.2.5。 onFulfilled 和 onRejected 必須作為函數呼叫(即沒有 this 值)。 ✅
2.2.6。 then 可能會針對同一個 Promise 被多次呼叫。 ✅
⟶ 如果/當 Promise 被履行時,所有對應的 onFulfilled 回呼必須按照它們最初呼叫 then 的順序執行。
⟶ 如果/當 Promise 被拒絕時,所有對應的 onRejected 回呼必須按照其原始呼叫 then 的順序執行。
2.2.7。那就必須回傳一個承諾。 ✅
promise.then(onFulfilled, onRejected);
⟶ 如果 onFulfilled 或 onRejected 傳回值 x,則執行 Promise 解析過程 [[Resolve]](promise2, x)。 ❌
⟶ 如果 onFulfilled 或 onRejected 拋出例外 e,則 Promise2 必須以 e 作為原因被拒絕。 ❌
⟶ 如果 onFulfilled 不是函數且 Promise1 已實現,則 Promise2 必須以與 Promise1 相同的值來實現。 ❌
⟶ 如果 onRejected 不是函數且 Promise1 被拒絕,則 Promise2 也必須以與 Promise1 相同的原因被拒絕。 ❌
JavaScript Promise 採用執行器函數作為參數,函數在 Promise 建立時立即呼叫:
promise2 = promise1.then(onFulfilled, onRejected);
new Promise(excecutor);
核心 Promises/A 規範不涉及如何建立、履行或拒絕 Promise。由你決定。但是您為 Promise 建構提供的實作必須與 JavaScript 中的非同步 API 相容。這是我們 Promise 類別的初稿:
const promise = new Promise((resolve, reject) => { // Runs some async or sync tasks });
規則 2.1(Promise 狀態)規定,Promise 必須處於以下三種狀態之一:待處理、已履行或已拒絕。它還解釋了每個狀態中發生的情況。
當履行或拒絕時,承諾不得轉變為任何其他狀態。因此,我們需要在進行任何轉換之前確保 Promise 處於待處理狀態:
class YourPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } }; try { executor(resolve, reject); // The executor function being called immediately } catch (error) { reject(error); } } }
我們已經知道 Promise 的初始狀態是待處理的,並且我們確保它保持這種狀態,直到明確履行或拒絕:
const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } };
由於執行器函數在 Promise 實例化後立即被調用,因此我們在建構子方法中調用它:
this.state = 'pending';
我們的 YourPromise 類別的初稿已在此處完成。
Promise/A 規範主要著重於定義可互通的 then() 方法。這個方法讓我們可以存取promise的當前或最終值或原因。讓我們深入探討一下。
規則 2.2(Then 方法)規定 Promise 必須有一個 then() 方法,該方法接受兩個參數:
try { executor(resolve, reject); } catch (error) { reject(error); }
onFulfilled 和 onRejected 都必須在 Promise 完成或拒絕後調用,如果它們是函數,則傳遞 Promise 的值或原因作為它們的第一個參數:
class YourPromise { constructor(executor) { // Implementation } then(onFulfilled, onRejected) { // Implementation } }
此外,在承諾履行或拒絕之前,不得調用它們,也不得調用超過一次。 onFulfilled 和 onRejected 都是可選的,如果它們不是函數,則應忽略它們。
如果你看規則2.2、2.2.6 和2.2.7,你會發現一個Promise 必須有一個then() 方法,then() 方法可以被多次調用,並且它必須返回一個承諾:
promise.then(onFulfilled, onRejected);
為了簡單起見,我們不會處理單獨的類別或函數。我們將傳回一個 Promise 對象,並傳遞一個執行器函數:
promise2 = promise1.then(onFulfilled, onRejected);
在執行器函數中,如果 Promise 被履行,我們會呼叫 onFulfilled 回呼並使用 Promise 的值來解析它。同樣,如果 Promise 被拒絕,我們會呼叫 onRejected 回呼並以 Promise 的原因拒絕它。
下一個問題是,如果 Promise 仍處於待處理狀態,如何處理 onFulfilled 和 onRejected 回呼?我們將它們排隊以便稍後調用,如下所示:
new Promise(excecutor);
我們完成了。這是 Promise 類別的第二稿,包括 then() 方法:
const promise = new Promise((resolve, reject) => { // Runs some async or sync tasks });
這裡,我們引入兩個欄位:onFulfilledCallbacks 和 onRejectedCallbacks 作為保存回調的佇列。當 Promise 未決時,這些佇列會透過 then() 呼叫填入回調,並且當 Promise 被履行或拒絕時呼叫它們。
繼續測試你的 Promise 類別:
class YourPromise { constructor(executor) { this.state = 'pending'; this.value = undefined; this.reason = undefined; const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } }; try { executor(resolve, reject); // The executor function being called immediately } catch (error) { reject(error); } } }
它應該輸出:
const resolve = value => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = reason => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; } };
另一方面,如果您執行以下測試:
this.state = 'pending';
你會得到:
try { executor(resolve, reject); } catch (error) { reject(error); }
代替:
class YourPromise { constructor(executor) { // Implementation } then(onFulfilled, onRejected) { // Implementation } }
為什麼?問題在於,當呼叫 then() 時 YourPromise 實例已被解析或拒絕時,then() 方法如何處理回呼。具體來說,當 Promise 狀態不是待處理時,then() 方法不會正確地將回呼的執行延遲到下一個微任務佇列。這會導致同步執行。在我們的範例測試中:
⟶ 承諾立即解決,值為「立即解決」。
⟶ 當呼叫promise.then()時,狀態已經完成,所以onFulfilled回呼會直接執行,不會延遲到下一個微任務佇列。
這裡規則 2.2.4 發揮作用。此規則可確保 then() 回呼(onFulfilled 或 onRejected)非同步執行,即使 Promise 已解決或拒絕。這意味著回調不得運行,直到當前執行堆疊完全清除並且只有平台程式碼(如事件循環或微任務佇列)正在運行。
這條規則是 Promise/A 規範中最重要的規則之一。因為它確保:
⟶ 即使 Promise 立即解決,其 then() 回調也不會執行,直到事件循環的下一個標記。
⟶ 此行為與 JavaScript 中其他非同步 API(例如 setTimeout 或 process.nextTick)的行為一致。
這可以透過巨集任務機制(如setTimeout或setImmediate)或微任務機制(如queueMicrotask或process.nextTick)來實現。因為微任務或巨集任務或類似機制中的回呼將在目前 JavaScript 執行上下文完成後執行。
為了解決上述問題,我們需要確保即使狀態已經完成或拒絕,相應的回調(onFulfilled或onRejected)也使用queueMicrotask非同步執行。這是修正後的實作:
promise.then(onFulfilled, onRejected);
再次執行前面的範例測試程式碼。您應該得到以下輸出:
promise2 = promise1.then(onFulfilled, onRejected);
就是這樣。
現在,您應該清楚地了解 then() 的回調如何延遲並在下一個微任務佇列中執行,從而實現異步行為。牢牢掌握這個概念對於在 JavaScript 中編寫有效的非同步程式碼至關重要。
下一步是什麼?由於本文沒有涵蓋完整的 Promises/A 規範,您可以嘗試實現其餘部分以獲得更深入的理解。
既然你已經讀到這裡了,希望你喜歡閱讀這篇文章!請分享文章。
追蹤我:
LinkedIn、Medium 和 Github
以上是在 JavaScript 中建立您自己的 Promise的詳細內容。更多資訊請關注PHP中文網其他相關文章!