让面试官为你停留——手写简易版Promise
JavaScript中的异步演进: 创构Promise及实现.then方法
一、编程背景的异步里程碑
JavaScript 是一个单线程语言,早期其处理网络请求、文件操作等耗时任务的方式较为原始且易于陷入回调地狱———这一问题直接影响了代码的维护性与可读性。从最初的回调函数 (Callbacks) 到现代的 async/await,JavaScript 的异步处理技术已经取得了长足的进展。以下是一个简要的历史回顾:
1. 回调函数: 最早的异步处理模式,容易导致代码嵌套过深和难以维护。
2. 事件监听器: 引入事件驱动的模式,提高异步操作的可管理性。
3. Promise: 采用链式调用,解决了回调地狱的问题,并加入错误处理机制。
4. Generator 函数与协程: 可以暂停函数执行,再逐步执行,使异步代码看起来更像同步代码。
5. Async/Await: 让异步代码写起来更加直观和便捷,是目前最新的异步处理标准。
我们今天重点介绍一下 Promise
,对其底层内容进行剖析,并手写一个能实现该功能的类。
二、简易版 Promise 实现思路
实现一个简易的 Promise 类(例如 MyPromise
),需要理解 Promise 的核心概念和行为。下面是一个完成简易 Promise 实现的基本思路:
1. 理解 Promise 的基本概念
- 状态:Promise 有三种状态:pending(初始状态)、fulfilled(已完成)和 rejected(已失败)。状态一旦从 pending 变为 fulfilled 或 rejected 就不能改变。
- 值:fulfilled 状态下 Promise 会携带一个值,而 rejected 状态下会携带一个原因(通常是错误对象)。
- 回调队列:在 pending 状态下,可以向 Promise 注册 onFulfilled 和 onRejected 回调函数。当状态改变时,这些回调会被调用。
2. 构建构造函数和状态逻辑
- 构造函数:在构造函数中初始化状态、值、原因和回调队列。定义
resolve
和reject
函数,它们负责改变状态并执行回调队列中的函数。 - 状态改变:
resolve
和reject
函数应确保状态只能从pending
变为 fulfilled 或 rejected,并且只能改变一次。
3. 实现 .then()
方法
- 参数处理:
.then()
方法应接收两个参数,分别为 onFulfilled 和 onRejected 函数。如果这些参数不是函数,则应提供默认行为。 - 返回新的 Promise:
.then()
方法必须返回一个新的 Promise,允许链式调用。这个新的 Promise 应该能够处理 onFulfilled 和 onRejected 函数的返回值或抛出的错误。 - 状态检查和回调执行:根据当前 Promise 的状态,决定是否立即执行 onFulfilled 或 onRejected 函数,或者将它们添加到回调队列中。
- 异步执行:使用
setTimeout
函数来异步执行回调函数,以确保在当前事件循环结束后执行。
4. 处理错误和异常
- 错误捕获:在执行 onFulfilled 和 onRejected 函数时,应捕获并处理可能抛出的异常,确保错误可以被后续的
.catch()
或.then()
处理。
5. 测试和调试
- 单元测试:编写测试用例,覆盖不同情况下的行为,如立即完成的 Promise、延迟完成的 Promise、错误传播、链式调用等。
- 调试和优化:根据测试结果调试代码,优化性能和可读性。
通过遵循上述思路,我们可以构建一个基本的 Promise 实现,能够处理异步操作并提供链式调用的能力。
三、.then 方法的实现思路
为了实现 MyPromise.prototype.then
方法,我们可以围绕几个关键点来构思和设计:
1. 参数处理
- 默认处理:如果
onFulfilled
或onRejected
不是函数,我们需要提供默认的行为。通常,onFulfilled
的默认行为是返回值本身,而onRejected
的默认行为是抛出错误。
2. 返回新的 Promise
- 链式调用:
then
方法应该返回一个新的 Promise,这允许链式调用。这意味着无论onFulfilled
或onRejected
返回什么,都必须包装成一个 Promise,并在then
方法内部处理。
3. 状态检查和异步执行
- 状态检查:我们需要检查当前 Promise 的状态(pending, fulfilled, rejected),并根据状态来决定下一步的操作。
- 异步执行:如果当前 Promise 已经是 fulfilled 或 rejected 状态,我们需要异步地执行对应的回调函数。这是因为 Promise 的规范要求在异步上下文中执行回调,即使它们是在同步代码中注册的。
4. 回调队列
- 回调注册:如果当前 Promise 仍处于 pending 状态,我们需要将
onFulfilled
和onRejected
函数存入各自的回调队列中。这些函数将在状态改变时被调用。
5. 错误处理
- 错误捕获:在执行
onFulfilled
或onRejected
函数时,我们需要捕获任何可能抛出的��误,并将其作为新的 rejected Promise 的原因。
6. 处理嵌套的 Promise
- Promise 解析:如果
onFulfilled
或onRejected
返回另一个 Promise,我们需要确保新的 Promise 正确地解析或拒绝该嵌套 Promise 的结果。
遵循这些步骤和考虑事项,我们就可以构建出一个.then
方法。
二、Promise的核心概念及其在JavaScript中的实现
本文主要关注Promise的机制及其实现。Promise是一种对异步操作结果的抽象表示,它有三种状态:pending(等待中)、fulfilled(已成功)和rejected(已失败)。下面是一个手动实现的Promise及其.then方法的简化版:
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined // 临时保存 resolve 中的参数
this.reason = undefined // 临时保存 reject 中的参数
this.onFulfilledCallbacks = [] // 装 then 中的回调
this.onRejectedCallbacks = [] // 装 catch 中的回调
const resolve = (value) => {
// 判断状态,保证 resolve,reject 只会执行一个状态
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
// cb 代表数组里面的每一项
this.onFulfilledCallbacks.forEach(cb => cb(value))
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
// cb 代表数组里面的每一项
this.onRejectedCallbacks.forEach(cb => cb(value))
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// 并且要返回一个 Promise(),这样才能实现接多个 .then 的效果
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
return new MyPromise((resolve, reject) => {
const fulfilledCallback = () => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
};
const rejectedCallback = () => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
};
if (this.state === 'fulfilled') {
setTimeout(fulfilledCallback, 0);
} else if (this.state === 'rejected') {
setTimeout(rejectedCallback, 0);
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(fulfilledCallback);
this.onRejectedCallbacks.push(rejectedCallback);
}
});
} then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
return new MyPromise((resolve, reject) => {
const fulfilledCallback = () => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
};
const rejectedCallback = () => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
};
if (this.state === 'fulfilled') {
setTimeout(fulfilledCallback, 0);
} else if (this.state === 'rejected') {
setTimeout(rejectedCallback, 0);
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(fulfilledCallback);
this.onRejectedCallbacks.push(rejectedCallback);
}
});
}
}
// 使用示例
new MyPromise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
resolve('成功');
}, 1000);
}).then(res => {
console.log(res); // 输出 "成功"
});
三、详细分析MyPromise实现与.then函数的机制
构造函数 constructor
-
初始化状态和队列:
state
:记录 Promise 的当前状态,初始为 'pending'。value
和reason
:分别用于保存成功和失败时的值。onFulfilledCallbacks
和onRejectedCallbacks
:用于存储在pending
状态下注册的回调函数。
-
resolve
和reject
函数:- 这些函数用于改变 Promise 的状态,并在状态改变时执行之前注册的回调函数。
- 如果状态已经是
fulfilled
或rejected
,则不执行任何操作,这是 Promise 的关键特性之一,即一旦状态被改变就不可再变。
-
执行器函数:
executor
是一个立即执行的函数,它接收resolve
和reject
函数作为参数。- 执行器函数中可能包含异步操作,如 AJAX 请求、计时器等。
- 包裹在
try-catch
块中,以捕捉并处理可能抛出的异常。
then
方法
-
默认处理:
- 如果
onFulfilled
或onRejected
不是函数,它们将被设置为默认函数。对于onFulfilled
,默认函数是返回传入的值;对于onRejected
,默认函数是抛出传入的错误。
- 如果
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason;
-
返回新的 Promise:
then
方法返回一个新的MyPromise
实例,这使得可以链式调用then
。- 新的
MyPromise
实例中,resolve
和reject
函数被用来处理由onFulfilled
或onRejected
返回的值或抛出的错误。
-
执行回调:
- 根据当前
MyPromise
实例的状态,决定立即执行回调还是将其添加到队列中等待状态改变时执行。
- 根据当前
总结
在使用示例中,我们创建了一个 MyPromise
实例,并在构造函数中使用 setTimeout
模拟了一个异步操作,最终调用 resolve
函数。然后我们链式调用了 then
方法,处理了 Promise
成功时的结果。
通过这个实现,我们可以看到 Promise
如何帮助我们管理和协调异步代码的执行流程。这不仅加深了我们对 Promise 工作机制的理解,还锻炼了编程技能。
转载自:https://juejin.cn/post/7392898777047433235