首頁 web前端 js教程 在 JavaScript 中建立您自己的 Promise

在 JavaScript 中建立您自己的 Promise

Dec 28, 2024 pm 01:29 PM

Create your own Promise in JavaScript

為什麼?

了解 JavaScript Promises 如何在背景非同步執行回呼。

讓我們用 JavaScript 創建自己的 Promise!我們將遵循 Promise/A 規範,該規範概述了 Promise 如何處理非同步操作、解析、拒絕以及確保可預測的連結和錯誤處理。

為了簡單起見,我們將重點放在 Promises/A 規範中 ✅ 標記的關鍵規則。這不是一個完整的實現,而是一個簡化版本。這是我們將要建構的:

1. 術語

1.1 'promise' 是一個帶有 then 方法的物件或函數,其行為符合此規範。

1.2 thenable'是一個定義了then方法的物件或函數。

1.3 'value' 是任何合法的 JavaScript 值(包括未定義的、thenable 或一個promise)。

1.4 「異常」是使用 throw 語句拋出的值。

1.5 'reason' 是一個值,表示承諾被拒絕的原因。

2. 要求

2.1 承諾狀態

承諾必須處於以下三種狀態之一:待定、已履行或已拒絕。

2.1.1。待處理時,承諾:✅

⟶ 可能會轉換為已完成或已拒絕狀態。

2.1.2。兌現後,承諾:✅

⟶不得轉換到任何其他狀態。

⟶ 必須有一個值,且不得更改。

2.1.3。當被拒絕時,一個承諾:✅

⟶不得轉換到任何其他狀態。

⟶必須有一個理由,而這個理由不能改變。

2.2 then方法

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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

前端熱敏紙小票打印遇到亂碼問題怎麼辦? 前端熱敏紙小票打印遇到亂碼問題怎麼辦? Apr 04, 2025 pm 02:42 PM

前端熱敏紙小票打印的常見問題與解決方案在前端開發中,小票打印是一個常見的需求。然而,很多開發者在實...

神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

誰得到更多的Python或JavaScript? 誰得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

JavaScript難以學習嗎? JavaScript難以學習嗎? Apr 03, 2025 am 12:20 AM

學習JavaScript不難,但有挑戰。 1)理解基礎概念如變量、數據類型、函數等。 2)掌握異步編程,通過事件循環實現。 3)使用DOM操作和Promise處理異步請求。 4)避免常見錯誤,使用調試技巧。 5)優化性能,遵循最佳實踐。

如何實現視差滾動和元素動畫效果,像資生堂官網那樣?
或者:
怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? 如何實現視差滾動和元素動畫效果,像資生堂官網那樣? 或者: 怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? Apr 04, 2025 pm 05:36 PM

實現視差滾動和元素動畫效果的探討本文將探討如何實現類似資生堂官網(https://www.shiseido.co.jp/sb/wonderland/)中�...

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? 如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

console.log輸出結果差異:兩次調用為何不同? console.log輸出結果差異:兩次調用為何不同? Apr 04, 2025 pm 05:12 PM

深入探討console.log輸出差異的根源本文將分析一段代碼中console.log函數輸出結果的差異,並解釋其背後的原因。 �...

See all articles