前言
本文将指导你一步步实现一个符合 Promises/A+规范的 Promise,帮助你深入理解 Promise 的工作原理和实现细节。
我们将使用一个名为HYPromise
的类来实现 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 类的基本结构以及构造函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class HYPromise { constructor(executor) { this.status = "pending"; this.value = undefined; this.reason = undefined;
const resolve = value => { if (this.status === "pending") { this.status = "fulfilled"; this.value = value; } };
const reject = reason => { if (this.status === "pending") { this.status = "rejected"; this.reason = reason; } };
try { executor(resolve, reject); } catch (error) { reject(error); } } }
|
在这一步中,我们定义了 HYPromise 类的基本结构,并实现了构造函数以及状态属性的初始化。我们还定义了resolve
和reject
方法,并在执行器函数中使用它们。如果执行器函数抛出异常,我们会捕获它并将 Promise 状态更改为 rejected。
第 2 步:实现 resolve 和 reject 两个核心方法
接下来,我们需要在 HYPromise 类中实现两个核心方法:resolve
和reject
。这两个方法用于处理异步操作的结果。我们会将这两个方法从构造函数中提取出来,并作为类的实例方法。同时,我们需要处理异步操作的结果,将成功或失败的处理函数存储在队列中,在resolve
或reject
方法中逐个执行这些处理函数。
下面是 HYPromise 类的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class HYPromise { constructor(executor) { this.status = "pending"; this.value = undefined; this.reason = undefined; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = [];
const resolve = value => { if (this.status === "pending") { this.status = "fulfilled"; this.value = value; this.onFulfilledCallbacks.forEach(callback => callback()); } };
const reject = reason => { if (this.status === "pending") { this.status = "rejected"; this.reason = reason; this.onRejectedCallbacks.forEach(callback => callback()); } };
try { executor(resolve, reject); } catch (error) { reject(error); } } }
|
在这一步中,我们实现了resolve
和reject
方法。当 Promise 状态从 pending 变为 fulfilled 或 rejected 时,我们将执行相应的处理函数队列中的函数。此外,我们还对构造函数中的异常处理进行了优化。
第 3 步:实现 then 方法
接下来,我们需要在 HYPromise 类中实现then
方法。then
方法用于为 Promise 实例注册成功和失败的处理函数。它返回一个新的 Promise 实例,以便我们可以链式调用。
为了符合 Promises/A+规范,我们需要实现一个名为resolvePromise
的辅助函数。这个函数用于处理then
方法返回的新 Promise 实例以及它们的成功和失败处理函数的结果。
首先,我们实现resolvePromise
辅助函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| function resolvePromise(promise2, x, resolve, reject) { if (promise2 === x) { return reject(new TypeError("Chaining cycle detected for promise")); }
let called = false;
if (x instanceof HYPromise) { x.then( y => { resolvePromise(promise2, y, resolve, reject); }, reason => { reject(reason); } ); } else if (x !== null && (typeof x === "object" || typeof x === "function")) { try { const then = x.then; if (typeof then === "function") { then.call( x, y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, reason => { if (called) return; called = true; reject(reason); } ); } else { resolve(x); } } catch (error) { if (called) return; called = true; reject(error); } } else { resolve(x); } }
|
接下来,我们在 HYPromise 类中实现then
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| class HYPromise {
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason; };
const promise2 = new HYPromise((resolve, reject) => { if (this.status === "fulfilled") { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); } else if (this.status === "rejected") { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); } else if (this.status === "pending") { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { const x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { const x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (error) { reject(error); } }); }); } }); return promise2; } }
|
第 4 步:实现 catch 方法
catch 方法是一个语法糖,它等价于调用 then 方法时仅传入一个失败处理函数。我们需要在 HYPromise 类中实现这个方法。
下面是 HYPromise 类的 catch 方法实现:
1 2 3 4 5 6 7 8
| class HYPromise {
catch(onRejected) { return this.then(null, onRejected); } }
|
在这一步中,我们实现了catch
方法。它只接受一个参数:onRejected
,这个参数是失败的处理函数。我们通过调用then
方法并传入null
作为成功处理函数来实现这个方法。
第 5 步:实现 finally 方法
finally 方法也是一个语法糖,它用于在 Promise 实例上注册一个处理函数,无论 Promise 是成功还是失败,该处理函数都会被调用。我们需要在 HYPromise 类中实现这个方法。
下面是 HYPromise 类的 finally 方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class HYPromise {
finally(callback) { return this.then( value => { return HYPromise.resolve(callback()).then(() => value); }, reason => { 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 静态方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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 静态方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 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 静态方法实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| 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+的测试库。在项目目录下运行以下命令:
1
| npm initnpm install --save-dev promises-aplus-tests
|
这将在项目中安装promises-aplus-tests
库。
第 2 步:编写测试适配器
接下来,我们需要编写一个适配器文件,以便promises-aplus-tests
库能够测试我们的 HYPromise 实现。在项目目录下创建一个名为adapter.js
的文件,然后在其中添加以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const HYPromise = require("./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
的文件,然后在其中添加以下代码:
1 2 3 4 5 6 7 8 9 10
| 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+ 测试通过”,否则会输出错误信息。
最后,运行以下命令执行测试:
如果我们的 HYPromise 实现正确,我们应该看到“Promises/A+ 测试通过”的输出。如果测试失败,我们需要根据错误信息修改我们的 HYPromise 实现,然后重新运行测试,直到所有测试都通过。
至此,我们已经成功地对我们的 HYPromise 实现进行了 Promises/A+测试。
文章转载于coderwhy | JavaScript 高级系列(十八) - 手写 Promise:实现符合 Promises/A+规范的 Promise