如何实现一个自定义的`Promise`,并解析其`then`、`catch`和`finally`的执行逻辑。

自定义 Promise 实现:深入解析 then、catch 和 finally

大家好!今天我们来一起深入探讨如何实现一个自定义的 Promise,并深入解析其 thencatchfinally 的执行逻辑。Promise 作为现代 JavaScript 中处理异步操作的重要工具,理解其内部机制对于编写高效、可维护的代码至关重要。

Promise 的基本概念

在开始实现之前,我们先回顾一下 Promise 的几个关键概念:

  • 状态 (State): Promise 具有三种状态:
    • Pending (待定): 初始状态,既没有被兑现,也没有被拒绝。
    • Fulfilled (已兑现): 操作成功完成。
    • Rejected (已拒绝): 操作失败。
  • 值 (Value): Promise 对象保存着一个值,该值在 Promise 状态变为 Fulfilled 时可用。
  • 原因 (Reason): Promise 对象也可能保存一个原因,该原因在 Promise 状态变为 Rejected 时可用。
  • 不可变性 (Immutability): 一旦 Promise 的状态变为 FulfilledRejected,它就不能再改变。
  • then 方法: 用于注册在 Promise 状态变为 FulfilledRejected 时要执行的回调函数。
  • catch 方法: 用于注册在 Promise 状态变为 Rejected 时要执行的回调函数,是 then(null, rejection) 的语法糖。
  • finally 方法: 用于注册在 Promise 状态变为 FulfilledRejected 时都要执行的回调函数,无论结果如何。

实现 Promise 骨架

首先,我们创建一个 MyPromise 类,并定义其构造函数和状态。

class MyPromise {
  constructor(executor) {
    this.state = 'pending'; // 初始状态为 pending
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    this.onFulfilledCallbacks = []; // 存储成功的回调
    this.onRejectedCallbacks = []; // 存储失败的回调

    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (error) {
      this.reject(error);
    }
  }

  resolve(value) {
    if (this.state === 'pending') {
      this.state = 'fulfilled';
      this.value = value;
      this.onFulfilledCallbacks.forEach(fn => fn()); // 执行所有成功的回调
    }
  }

  reject(reason) {
    if (this.state === 'pending') {
      this.state = 'rejected';
      this.reason = reason;
      this.onRejectedCallbacks.forEach(fn => fn()); // 执行所有失败的回调
    }
  }

  then(onFulfilled, onRejected) {
    // ... 实现 then 方法
  }

  catch(onRejected) {
    return this.then(null, onRejected); // catch 是 then 的语法糖
  }

  finally(callback) {
    // ... 实现 finally 方法
  }
}

在构造函数中,我们接收一个 executor 函数,该函数接收 resolvereject 两个函数作为参数。executor 函数负责执行异步操作,并在操作成功时调用 resolve,操作失败时调用 reject

我们还定义了 onFulfilledCallbacksonRejectedCallbacks 数组,用于存储 then 方法注册的回调函数。当 Promise 的状态变为 fulfilledrejected 时,我们会依次执行这些回调函数。

实现 then 方法

then 方法是 Promise 的核心方法,它用于注册在 Promise 状态变为 fulfilledrejected 时要执行的回调函数。then 方法返回一个新的 Promise 对象,允许链式调用。

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }

这个 then 方法的实现包含以下几个关键步骤:

  1. 参数处理: 首先,我们对 onFulfilledonRejected 进行类型检查。如果它们不是函数,则提供默认的函数:
    • onFulfilled 默认函数返回 value,实现值的穿透。
    • onRejected 默认函数抛出 reason,实现错误穿透。
  2. 创建新的 Promise: 创建一个新的 MyPromise 对象 promise2,并返回它。这是实现链式调用的关键。
  3. 处理不同的状态: 根据当前 Promise 的状态,执行不同的逻辑:
    • fulfilled: 异步执行 onFulfilled,并将结果 x 传递给 resolvePromise 函数。
    • rejected: 异步执行 onRejected,并将结果 x 传递给 resolvePromise 函数。
    • pending:onFulfilledonRejected 函数分别添加到 onFulfilledCallbacksonRejectedCallbacks 数组中,等待 Promise 状态改变时执行。 使用setTimeout是为了模拟异步,保证promise2创建完成
  4. 异步执行: 使用 setTimeout 模拟异步执行,确保在当前调用栈清空后执行回调函数。
  5. resolvePromise 函数: 这个函数用于处理 onFulfilledonRejected 回调函数的返回值 x,并根据 x 的类型来决定 promise2 的状态。 这个函数非常重要,处理了then返回的值x的情况,让then可以链式调用。

实现 resolvePromise 函数

resolvePromise 函数用于处理 onFulfilledonRejected 回调函数的返回值 x,并根据 x 的类型来决定 promise2 的状态。这是 Promise 实现中最复杂的部分。

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'));
  }

  let called = false; // 防止多次调用 resolve 或 reject

  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    try {
      const then = x.then; // 取出 x 的 then 方法
      if (typeof then === 'function') {
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject); // 递归调用 resolvePromise
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    if (called) return;
    called = true;
    resolve(x);
  }
}

resolvePromise 函数的逻辑如下:

  1. 循环引用检测: 首先,检查 promise2 是否等于 x。如果相等,说明发生了循环引用,抛出一个 TypeError
  2. 判断 x 的类型: 判断 x 是否为对象或函数。
    • 如果 x 是对象或函数:
      • 尝试取出 xthen 方法。
      • 如果 then 是一个函数,则认为 x 是一个 Promise 对象。调用 then 方法,并将 resolvereject 作为参数传递给它。
      • 如果 then 不是一个函数,则将 x 作为值传递给 resolve 函数,使 promise2 变为 fulfilled 状态。
    • 如果 x 不是对象或函数:x 作为值传递给 resolve 函数,使 promise2 变为 fulfilled 状态。
  3. 防止多次调用: 使用 called 变量来防止多次调用 resolvereject 函数。
  4. 错误处理: 使用 try...catch 块来捕获可能发生的错误,并将错误传递给 reject 函数,使 promise2 变为 rejected 状态。

实现 finally 方法

finally 方法用于注册在 Promise 状态变为 fulfilledrejected 时都要执行的回调函数,无论结果如何。finally 方法返回一个新的 Promise 对象,并将原始 Promise 的值或原因传递给它。

  finally(callback) {
    return this.then(
      value => {
        return MyPromise.resolve(callback()).then(() => value);
      },
      reason => {
        return MyPromise.resolve(callback()).then(() => { throw reason; });
      }
    );
  }

finally 方法的实现原理如下:

  1. 使用 then 方法: finally 方法实际上是基于 then 方法实现的。
  2. 无论成功或失败都执行回调:then 方法的 onFulfilledonRejected 回调函数中,都调用 callback 函数。
  3. 返回原始值或原因:callback 函数执行完毕后,返回原始 Promise 的值或原因,以保持 Promise 链的完整性。
    • 如果原始 Promise 状态为 fulfilled,则返回 value
    • 如果原始 Promise 状态为 rejected,则抛出 reason
  4. 确保回调异步执行完成: 使用 MyPromise.resolve(callback()) 确保 callback 函数异步执行完成,避免阻塞 Promise 链。

完善 Promise 实现:静态方法

为了使我们的 MyPromise 更完整,我们还需要实现一些静态方法,例如 resolverejectall

MyPromise.resolve(value):

  static resolve(value) {
    return new MyPromise((resolve, reject) => {
      resolve(value);
    });
  }

MyPromise.reject(reason):

  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }

MyPromise.all(promises):

  static all(promises) {
    return new MyPromise((resolve, reject) => {
      if (!Array.isArray(promises)) {
        return reject(new TypeError('Argument must be an array'));
      }

      const result = [];
      let count = 0;

      if (promises.length === 0) {
        return resolve(result);
      }

      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];

        MyPromise.resolve(promise).then(
          value => {
            result[i] = value;
            count++;

            if (count === promises.length) {
              resolve(result);
            }
          },
          reason => {
            reject(reason);
          }
        );
      }
    });
  }

MyPromise.all(promises) 方法的逻辑如下:

  1. 参数校验: 首先,检查 promises 是否为数组。如果不是,则抛出一个 TypeError
  2. 初始化: 创建一个空数组 result 用于存储结果,并初始化计数器 count 为 0。
  3. 空数组处理: 如果 promises 数组为空,则直接返回一个 fulfilled 状态的 Promise,值为 result
  4. 遍历 promises 数组: 遍历 promises 数组,并将每个元素转换为 Promise 对象。
  5. 处理每个 Promise 对象: 对于每个 Promise 对象,使用 then 方法注册回调函数:
    • 成功回调:Promise 的值添加到 result 数组中,并将计数器 count 加 1。如果 count 等于 promises 数组的长度,则说明所有 Promise 对象都已成功,将 result 作为值传递给 resolve 函数,使 Promise 变为 fulfilled 状态。
    • 失败回调:reason 作为原因传递给 reject 函数,使 Promise 变为 rejected 状态。

测试我们的 Promise 实现

现在,我们可以使用一些简单的测试用例来验证我们的 MyPromise 实现是否正确。

const promise1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise 1 resolved');
  }, 1000);
});

promise1
  .then(value => {
    console.log('then 1:', value);
    return 'then 1 return';
  })
  .then(value => {
    console.log('then 2:', value);
    throw new Error('Error in then 2');
  })
  .catch(error => {
    console.error('catch:', error);
  })
  .finally(() => {
    console.log('finally');
  });

MyPromise.resolve('Resolved value')
  .then(value => console.log('Resolved promise:', value));

MyPromise.reject('Rejected reason')
  .catch(reason => console.error('Rejected promise:', reason));

MyPromise.all([
  MyPromise.resolve(1),
  new MyPromise(resolve => setTimeout(() => resolve(2), 500)),
  3,
])
  .then(values => console.log('All resolved:', values))
  .catch(reason => console.error('All rejected:', reason));

总结重点

我们深入探讨了如何实现一个自定义的 Promise,并详细解析了 thencatchfinally 的执行逻辑。重点在于对 Promise 状态的维护、回调函数的管理、以及 resolvePromise 函数的处理,它决定了链式调用中 Promise 的状态传递和结果处理。

进一步的思考方向

  • 异步任务调度: 研究如何优化异步任务的调度,例如使用微任务队列(microtask queue)来提高性能。
  • Promise A+ 规范: 深入理解 Promise A+ 规范,确保你的 Promise 实现符合标准。
  • 错误处理: 探索更高级的错误处理机制,例如使用 unhandledRejection 事件来捕获未处理的 Promise 拒绝。

希望今天的分享对大家有所帮助,谢谢!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注