likes
comments
collection
share

让面试官为你停留——手写简易版Promise

作者站长头像
站长
· 阅读数 22

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

  1. 初始化状态和队列

    • state:记录 Promise 的当前状态,初始为 'pending'。
    • value 和 reason:分别用于保存成功和失败时的值。
    • onFulfilledCallbacks 和 onRejectedCallbacks:用于存储在 pending 状态下注册的回调函数。
  2. resolvereject 函数

    • 这些函数用于改变 Promise 的状态,并在状态改变时执行之前注册的回调函数。
    • 如果状态已经是 fulfilled 或 rejected,则不执行任何操作,这是 Promise 的关键特性之一,即一旦状态被改变就不可再变。
  3. 执行器函数

    • executor 是一个立即执行的函数,它接收 resolve 和 reject 函数作为参数。
    • 执行器函数中可能包含异步操作,如 AJAX 请求、计时器等。
    • 包裹在 try-catch 块中,以捕捉并处理可能抛出的异常。

then 方法

  1. 默认处理

    • 如果 onFulfilled 或 onRejected 不是函数,它们将被设置为默认函数。对于 onFulfilled,默认函数是返回传入的值;对于 onRejected,默认函数是抛出传入的错误。
 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
 
 onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; 
  1. 返回新的 Promise

    • then 方法返回一个新的 MyPromise 实例,这使得可以链式调用 then
    • 新的 MyPromise 实例中,resolve 和 reject 函数被用来处理由 onFulfilled 或 onRejected 返回的值或抛出的错误。
  2. 执行回调

    • 根据当前 MyPromise 实例的状态,决定立即执行回调还是将其添加到队列中等待状态改变时执行。

总结

在使用示例中,我们创建了一个 MyPromise 实例,并在构造函数中使用 setTimeout 模拟了一个异步操作,最终调用 resolve 函数。然后我们链式调用了 then 方法,处理了 Promise 成功时的结果。

通过这个实现,我们可以看到 Promise 如何帮助我们管理和协调异步代码的执行流程。这不仅加深了我们对 Promise 工作机制的理解,还锻炼了编程技能。

转载自:https://juejin.cn/post/7392898777047433235
评论
请登录