手寫 Promise:實現符合 Promises-A - 規範的 Promise
本文將指導你一步步實現一個符合 Promises/A + 規範的 Promise,幫助你深入理解 Promise 的工作原理和實現細節。
一. 整體介紹
Promise 是 JavaScript 中非常重要的一個概念,它是一種用於處理異步操作的編程模型。Promise 提供了一種優雅的方式來處理異步操作的成功或失敗,以及它們之間的依賴關係。這使得我們可以避免回調地獄(Callback Hell)的問題,編寫更清晰、更易於理解的代碼。
Promises/A + 是一個對 Promise 行爲的開放規範,它規定了 Promise 應該如何表現和實現。遵循這個規範的 Promise 實現可以確保它們之間的互操作性,使得我們可以在不同的庫和框架中輕鬆地使用它們。在本文中,我們將實現一個名爲HYPromise
的類,它將遵循 Promises/A + 規範。
二. 實現目標
我們將實現以下功能和方法,使HYPromise
符合 Promises/A + 規範:
-
HYPromise 構造函數及基本狀態
-
resolve 和 reject 方法
-
then 方法
-
catch 方法
-
finally 方法
-
HYPromise.resolve 和 HYPromise.reject 靜態方法
-
HYPromise.all 和 HYPromise.race 靜態方法
-
實現 Promise.allSettled 和 Promise.any 靜態方法
三. 實現過程
第 1 步:定義 HYPromise 構造函數,並實現基本的狀態屬性
首先,我們需要創建一個名爲HYPromise
的類,並定義三種狀態:pending、fulfilled 和 rejected。HYPromise 類的構造函數將接收一個執行器(executor)函數作爲參數。執行器函數會立即執行,並接收兩個參數:resolve
和reject
,它們分別用於將 Promise 狀態從 pending 更改爲 fulfilled 或 rejected。
我們還需要在類中實現狀態的改變以及狀態改變時對應的值(value)或原因(reason)的存儲。
下面是 HYPromise 類的基本結構以及構造函數的實現:
class HYPromise {
constructor(executor) {
// 初始化狀態爲pending
this.status = 'pending';
// 初始化成功的值爲undefined
this.value = undefined;
// 初始化失敗的原因爲undefined
this.reason = undefined;
// 定義resolve方法
const resolve = (value) => {
// 只有在pending狀態才能更改狀態和值
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
}
};
// 定義reject方法
const reject = (reason) => {
// 只有在pending狀態才能更改狀態和原因
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
}
};
// 立即執行執行器函數
try {
executor(resolve, reject);
} catch (error) {
// 如果執行器函數拋出異常,將Promise狀態更改爲rejected
reject(error);
}
}
}
在這一步中,我們定義了 HYPromise 類的基本結構,並實現了構造函數以及狀態屬性的初始化。我們還定義了resolve
和reject
方法,並在執行器函數中使用它們。如果執行器函數拋出異常,我們會捕獲它並將 Promise 狀態更改爲 rejected。
第 2 步:實現 resolve 和 reject 兩個核心方法
接下來,我們需要在 HYPromise 類中實現兩個核心方法:resolve
和reject
。這兩個方法用於處理異步操作的結果。我們會將這兩個方法從構造函數中提取出來,並作爲類的實例方法。同時,我們需要處理異步操作的結果,將成功或失敗的處理函數存儲在隊列中,在resolve
或reject
方法中逐個執行這些處理函數。
下面是 HYPromise 類的實現:
class HYPromise {
constructor(executor) {
// 初始化狀態爲pending
this.status = 'pending';
// 初始化成功的值爲undefined
this.value = undefined;
// 初始化失敗的原因爲undefined
this.reason = undefined;
// 初始化成功處理函數隊列
this.onFulfilledCallbacks = [];
// 初始化失敗處理函數隊列
this.onRejectedCallbacks = [];
// 定義resolve方法
const resolve = (value) => {
// 只有在pending狀態才能更改狀態和值
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
// 執行所有成功處理函數
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
// 定義reject方法
const reject = (reason) => {
// 只有在pending狀態才能更改狀態和原因
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
// 執行所有失敗處理函數
this.onRejectedCallbacks.forEach(callback => callback());
}
};
// 立即執行執行器函數
try {
executor(resolve, reject);
} catch (error) {
// 如果執行器函數拋出異常,將Promise狀態更改爲rejected
reject(error);
}
}
}
在這一步中,我們實現了resolve
和reject
方法。當 Promise 狀態從 pending 變爲 fulfilled 或 rejected 時,我們將執行相應的處理函數隊列中的函數。此外,我們還對構造函數中的異常處理進行了優化。
第 3 步:實現 then 方法
接下來,我們需要在 HYPromise 類中實現then
方法。then
方法用於爲 Promise 實例註冊成功和失敗的處理函數。它返回一個新的 Promise 實例,以便我們可以鏈式調用。
爲了符合 Promises/A + 規範,我們需要實現一個名爲resolvePromise
的輔助函數。這個函數用於處理then
方法返回的新 Promise 實例以及它們的成功和失敗處理函數的結果。
首先,我們實現resolvePromise
輔助函數:
function resolvePromise(promise2, x, resolve, reject) {
// 1. 如果 promise2 和 x 相同,拋出 TypeError
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 標記是否已調用,防止多次調用
let called = false;
// 2. 如果 x 是 HYPromise 實例
if (x instanceof HYPromise) {
// 根據 x 的狀態調用 resolve 或 reject
x.then(
y => {
resolvePromise(promise2, y, resolve, reject);
},
reason => {
reject(reason);
}
);
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) { // 3. 如果 x 是對象或函數
try {
// 獲取 x 的 then 方法
const then = x.then;
if (typeof then === 'function') { // 如果 then 是函數
// 使用 x 作爲上下文調用 then 方法
then.call(
x,
y => { // 成功回調
if (called) return; // 如果已經調用過,直接返回
called = true;
// 遞歸處理 y
resolvePromise(promise2, y, resolve, reject);
},
reason => { // 失敗回調
if (called) return; // 如果已經調用過,直接返回
called = true;
reject(reason);
}
);
} else { // 如果 then 不是函數
// 直接調用 resolve
resolve(x);
}
} catch (error) { // 如果獲取或調用 then 方法拋出異常
if (called) return; // 如果已經調用過,直接返回
called = true;
reject(error);
}
} else { // 4. 如果 x 不是對象或函數
// 直接調用 resolve
resolve(x);
}
}
接下來,我們在 HYPromise 類中實現then
方法:
then(onFulfilled, onRejected) {
// 如果不傳處理函數,則使用默認處理函數
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
// 創建一個新的Promise實例,稱爲promise2
const promise2 = new HYPromise((resolve, reject) => {
if (this.status === 'fulfilled') {
// 使用setTimeout保證異步調用
setTimeout(() => {
try {
// 調用onFulfilled,並獲取返回值
const x = onFulfilled(this.value);
// 使用返回值x和新的Promise實例promise2來處理resolve和reject
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 如果處理函數拋出異常,則將promise2狀態更改爲rejected
reject(error);
}
});
} else if (this.status === 'rejected') {
// 使用setTimeout保證異步調用
setTimeout(() => {
try {
// 調用onRejected,並獲取返回值
const x = onRejected(this.reason);
// 使用返回值x和新的Promise實例promise2來處理resolve和reject
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 如果處理函數拋出異常,則將promise2狀態更改爲rejected
reject(error);
}
});
} else if (this.status === 'pending') {
// 如果當前Promise狀態仍爲pending,將處理函數加入相應的隊列中
this.onFulfilledCallbacks.push(() => {
// 使用setTimeout保證異步調用
setTimeout(() => {
try {
// 調用onFulfilled,並獲取返回值
const x = onFulfilled(this.value);
// 使用返回值x和新的Promise實例promise2來處理resolve和reject
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 如果處理函數拋出異常,則將promise2狀態更改爲rejected
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
// 使用setTimeout保證異步調用
setTimeout(() => {
try {
// 調用onRejected,並獲取返回值
const x = onRejected(this.reason);
// 使用返回值x和新的Promise實例promise2來處理resolve和reject
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
// 如果處理函數拋出異常,則將promise2狀態更改爲rejected
reject(error);
}
});
});
}
});
// 返回新的Promise實例,以便鏈式調用
return promise2;
}
第 4 步:實現 catch 方法
catch 方法是一個語法糖,它等價於調用 then 方法時僅傳入一個失敗處理函數。我們需要在 HYPromise 類中實現這個方法。
下面是 HYPromise 類的 catch 方法實現:
class HYPromise {
// ...其他代碼
catch(onRejected) {
// 調用then方法,僅傳入失敗處理函數
return this.then(null, onRejected);
}
}
在這一步中,我們實現了catch
方法。它只接受一個參數:onRejected
,這個參數是失敗的處理函數。我們通過調用then
方法並傳入null
作爲成功處理函數來實現這個方法。
第 5 步:實現 finally 方法
finally 方法也是一個語法糖,它用於在 Promise 實例上註冊一個處理函數,無論 Promise 是成功還是失敗,該處理函數都會被調用。我們需要在 HYPromise 類中實現這個方法。
下面是 HYPromise 類的 finally 方法實現:
class HYPromise {
// ...其他代碼
finally(callback) {
// 調用then方法,傳入兩個相同的處理函數
return this.then(
value => {
// 創建一個新的Promise實例,確保異步執行callback
return HYPromise.resolve(callback()).then(() => value);
},
reason => {
// 創建一個新的Promise實例,確保異步執行callback
return HYPromise.resolve(callback()).then(() => { throw reason; });
}
);
}
}
在這一步中,我們實現了finally
方法。它接受一個參數:callback
,這個參數是一個處理函數。無論 Promise 實例成功還是失敗,這個處理函數都會被調用。我們通過調用then
方法並傳入兩個相同的處理函數來實現這個方法。這兩個處理函數分別用於成功和失敗的情況,它們都會返回一個新的 Promise 實例,以確保callback
是異步執行的。
第 6 步:實現 Promise.resolve 和 Promise.reject 靜態方法
接下來,我們需要在 HYPromise 類中實現兩個靜態方法:resolve
和reject
。這兩個方法可以快速地創建一個已經解決或拒絕的 Promise 實例。
下面是 HYPromise 類的 resolve 和 reject 靜態方法實現:
class HYPromise {
// ...其他代碼
static resolve(value) {
if (value instanceof HYPromise) {
return value;
}
return new HYPromise((resolve, reject) => {
resolve(value);
});
}
static reject(reason) {
return new HYPromise((resolve, reject) => {
reject(reason);
});
}
}
在這一步中,我們實現了resolve
和reject
靜態方法。resolve
方法接受一個參數:value
,用於創建一個已經解決的 Promise 實例。reject
方法接受一個參數:reason
,用於創建一個已經拒絕的 Promise 實例。
第 7 步:實現 Promise.all 和 Promise.race 靜態方法
最後,我們需要在 HYPromise 類中實現兩個靜態方法:all
和race
。all
方法用於將多個 Promise 實例包裝成一個新的 Promise 實例,只有當所有的 Promise 實例都成功時,新的 Promise 實例纔會成功;race
方法則是將多個 Promise 實例包裝成一個新的 Promise 實例,只要其中一個 Promise 實例成功或失敗,新的 Promise 實例就會立即成功或失敗。
下面是 HYPromise 類的 all 和 race 靜態方法實現:
class HYPromise {
// ...其他代碼
static all(promises) {
return new HYPromise((resolve, reject) => {
const result = [];
let resolvedCount = 0;
promises.forEach((promise, index) => {
HYPromise.resolve(promise).then(
value => {
result[index] = value;
resolvedCount++;
if (resolvedCount === promises.length) {
resolve(result);
}
},
reason => {
reject(reason);
}
);
});
});
}
static race(promises) {
return new HYPromise((resolve, reject) => {
promises.forEach(promise => {
HYPromise.resolve(promise).then(
value => {
resolve(value);
},
reason => {
reject(reason);
}
);
});
});
}
}
在這一步中,我們實現了all
和race
靜態方法。all
方法接受一個數組參數,該數組包含多個 Promise 實例。我們遍歷這個數組,使用HYPromise.resolve
將每個實例包裝成一個標準的 Promise 實例。當所有實例都解決時,我們將結果數組傳遞給新的 Promise 實例的resolve
方法。race
方法的實現類似,我們遍歷輸入數組,當任何一個實例解決或拒絕時,立即調用新的 Promise 實例的resolve
或reject
方法。
第 8 步:實現 Promise.allSettled 和 Promise.any 靜態方法
接下來,我們需要在 HYPromise 類中實現兩個額外的靜態方法:allSettled
和any
。allSettled
方法用於將多個 Promise 實例包裝成一個新的 Promise 實例,只要所有的 Promise 實例都完成(成功或失敗),新的 Promise 實例就會成功;any
方法則是將多個 Promise 實例包裝成一個新的 Promise 實例,只要其中一個 Promise 實例成功,新的 Promise 實例就會立即成功。如果所有實例都失敗,新的 Promise 實例將失敗。
下面是 HYPromise 類的 allSettled 和 any 靜態方法實現:
class HYPromise {
// ...其他代碼
static allSettled(promises) {
return new HYPromise((resolve, reject) => {
const result = [];
let settledCount = 0;
promises.forEach((promise, index) => {
HYPromise.resolve(promise).then(
value => {
result[index] = { status: 'fulfilled', value };
settledCount++;
if (settledCount === promises.length) {
resolve(result);
}
},
reason => {
result[index] = { status: 'rejected', reason };
settledCount++;
if (settledCount === promises.length) {
resolve(result);
}
}
);
});
});
}
static any(promises) {
return new HYPromise((resolve, reject) => {
const errors = [];
let rejectedCount = 0;
promises.forEach((promise, index) => {
HYPromise.resolve(promise).then(
value => {
resolve(value);
},
reason => {
errors[index] = reason;
rejectedCount++;
if (rejectedCount === promises.length) {
reject(new AggregateError(errors, 'All promises were rejected'));
}
}
);
});
});
}
}
在這一步中,我們實現了allSettled
和any
靜態方法。allSettled
方法接受一個數組參數,該數組包含多個 Promise 實例。我們遍歷這個數組,使用HYPromise.resolve
將每個實例包裝成一個標準的 Promise 實例。當所有實例都完成(成功或失敗)時,我們將結果數組傳遞給新的 Promise 實例的resolve
方法。any
方法的實現類似,我們遍歷輸入數組,當任何一個實例解決時,立即調用新的 Promise 實例的resolve
方法。當所有實例都拒絕時,我們將錯誤數組傳遞給新的 Promise 實例的reject
方法。
四. Promises/A+ 測試
在實現完我們的 HYPromise 之後,我們需要通過 Promises/A + 的測試來驗證我們的實現是否正確。Promises/A + 提供了一組測試用例,我們可以用這些測試用例來確保我們的 HYPromise 滿足 Promises/A + 規範。
第 1 步:安裝 Promises/A + 測試庫
首先,我們需要安裝 Promises/A + 的測試庫。在項目目錄下運行以下命令:
npm init
npm install --save-dev promises-aplus-tests
這將在項目中安裝promises-aplus-tests
庫。
第 2 步:編寫測試適配器
接下來,我們需要編寫一個適配器文件,以便promises-aplus-tests
庫能夠測試我們的 HYPromise 實現。在項目目錄下創建一個名爲adapter.js
的文件,然後在其中添加以下代碼:
const HYPromise = require('./HYPromise'); // 導入我們實現的HYPromise模塊
// 暴露適配器對象
module.exports = {
resolved: HYPromise.resolve,
rejected: HYPromise.reject,
deferred() {
const result = {};
result.promise = new HYPromise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
};
這個適配器文件導出了一個對象,其中包含了resolved
、rejected
和deferred
方法。這些方法分別對應 HYPromise 的resolve
、reject
方法和一個返回延遲對象(包含一個新的 Promise 實例以及對應的 resolve 和 reject 方法)的函數。
第 3 步:運行測試
在項目目錄下創建一個名爲test.js
的文件,然後在其中添加以下代碼:
const promisesAplusTests = require('promises-aplus-tests');
const adapter = require('./adapter');
promisesAplusTests(adapter, function (err) {
if (err) {
console.error('Promises/A+ 測試失敗:');
console.error(err);
} else {
console.log('Promises/A+ 測試通過');
}
});
這個文件導入了promises-aplus-tests
庫和我們編寫的適配器。然後,我們調用promisesAplusTests
函數,傳入適配器對象和一個回調函數。如果測試通過,我們會在控制檯輸出 “Promises/A+ 測試通過”,否則會輸出錯誤信息。
最後,運行以下命令執行測試:
node test.js
如果我們的 HYPromise 實現正確,我們應該看到 “Promises/A+ 測試通過” 的輸出。如果測試失敗,我們需要根據錯誤信息修改我們的 HYPromise 實現,然後重新運行測試,直到所有測試都通過。
至此,我們已經成功地對我們的 HYPromise 實現進行了 Promises/A + 測試。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/L2dl0woiRl6ghyKoo-sWNA