Skip to content

手动实现 promise

要手动实现一个简单版的 Promise,需要理解它的基本结构和工作原理。Promise 是一个用于处理异步操作的对象,主要有三种状态:

  1. pending(等待态):初始状态,既没有被解决也没有被拒绝。
  2. fulfilled(已解决态):操作成功完成。
  3. rejected(已拒绝态):操作失败。

Promise 状态变为 fulfilledrejected 时,它不能再次改变。同时,Promise 还支持链式调用,通过 .then().catch() 来注册回调函数处理不同状态。

下面是一个简单的 Promise 实现:

手动实现 Promise

js
class MyPromise {
  constructor(executor) {
    // 初始化状态
    this.state = 'pending';  // Promise 初始状态为 pending
    this.value = undefined;  // 成功时返回的值
    this.reason = undefined; // 失败时返回的原因

    // 用于存储成功与失败回调函数
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    // 定义 resolve 方法
    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'; // 将状态改为成功
        this.value = value;        // 保存成功的值
        // 执行所有成功的回调函数
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    // 定义 reject 方法
    const reject = (reason) => {
      if (this.state === 'pending') {
        this.state = 'rejected';  // 将状态改为失败
        this.reason = reason;     // 保存失败的原因
        // 执行所有失败的回调函数
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    // 执行传入的 executor 函数
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error); // 如果执行过程中发生异常,直接 reject
    }
  }

  // then 方法实现
  then(onFulfilled, onRejected) {
    // 如果没有传入 onRejected,则默认抛出 reason
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
    // 如果没有传入 onFulfilled,则默认返回 value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;

    // 返回一个新的 Promise,支持链式调用
    return new MyPromise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        // 异步执行,保证 then 函数在当前事件循环结束后执行
        setTimeout(() => {
          try {
            const result = onFulfilled(this.value);
            resolve(result); // 处理成功回调
          } catch (error) {
            reject(error); // 捕获回调中的异常
          }
        });
      } else if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });
      } else if (this.state === 'pending') {
        // 如果 Promise 还在 pending 状态,将回调函数存起来
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onFulfilled(this.value);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onRejected(this.reason);
              resolve(result);
            } catch (error) {
              reject(error);
            }
          });
        });
      }
    });
  }

  // catch 方法实现,实际上就是 then 的语法糖
  catch(onRejected) {
    return this.then(null, onRejected);
  }
}

解释实现原理:

  1. 状态管理

    • Promise 的状态只能从 pending 变为 fulfilledrejected,并且一旦状态改变后就不能再次改变。
    • 使用 this.state 来管理 Promise 的当前状态。
  2. 回调存储

    • Promise 处于 pending 状态时,then 方法中的成功或失败回调并不会立即执行。因此,我们用两个数组 onFulfilledCallbacksonRejectedCallbacks 分别存储回调,等到状态改变后再依次执行它们。
  3. 异步处理

    • 根据 Promise 规范,then 中的回调函数必须异步执行。因此,使用 setTimeout 来模拟异步行为,使得回调不会在当前事件循环中立即执行。
  4. 链式调用

    • 每次调用 then 方法都会返回一个新的 Promise,以便进行链式调用。
    • 通过 try...catch 捕获回调函数中的错误,如果出现错误,直接调用 reject
  5. catch 方法

    • catchthen 的简化版本,用于处理 reject 的情况,相当于 then(null, onRejected)

使用示例:

js
const p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功');
  }, 1000);
});

p.then((value) => {
  console.log(value); // 输出: 成功
  return '继续链式调用';
}).then((value) => {
  console.log(value); // 输出: 继续链式调用
}).catch((err) => {
  console.error(err);
});

这段代码展示了如何手动实现一个简化版的 Promise,并支持异步操作、状态管理和链式调用。

手动实现 Promise.all

要手动实现一个类似于 Promise.all 的功能,可以创建一个函数来接收一组 promise,并返回一个新的 promise。这个新 promise 只有在所有输入的 promise 都成功时才会成功,如果任何一个 promise 失败,它将立即失败。我们可以使用 Promise 的基本原理来构建这个逻辑。

下面是手动实现 Promise.all 的示例:

javascript
function promiseAll(promises) {
  return new Promise((resolve, reject) => {
    // 检查传入的是否为一个数组
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }

    let resolvedCount = 0;      // 记录成功的 promise 数量
    const results = [];         // 存储每个 promise 的结果
    const promisesLength = promises.length;

    // 如果传入的数组为空,立即 resolve 一个空数组
    if (promisesLength === 0) {
      return resolve(results);
    }

    promises.forEach((promise, index) => {
      // 确保每个项是一个 promise,使用 Promise.resolve 处理
      Promise.resolve(promise)
        .then((value) => {
          // 将结果保存到对应的索引位置
          results[index] = value;
          resolvedCount++;

          // 当所有的 promise 都 resolve 时,resolve 整个结果数组
          if (resolvedCount === promisesLength) {
            resolve(results);
          }
        })
        .catch((error) => {
          // 一旦有任何一个 promise 失败,立即 reject
          reject(error);
        });
    });
  });
}

// 使用示例
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'foo'));
const promise3 = Promise.resolve(42);

promiseAll([promise1, promise2, promise3])
  .then((results) => console.log(results))  // 输出: [3, 'foo', 42]
  .catch((error) => console.log(error));

解释:

  1. 参数检查:首先,检查传入的参数是否为数组。如果不是,直接 reject 一个 TypeError
  2. Promise 处理
    • 我们使用 Promise.resolve 来确保每个项都是一个 promise(即便它可能是一个普通值)。
    • 每次 promise 成功时,我们将结果存储在 results 数组中,保持与输入数组相同的索引位置。
  3. 计数:当每个 promise 成功时,resolvedCount 增加。当这个计数等于输入 promise 的总数时,表示所有 promise 都已经成功,最终 resolve 整个结果数组。
  4. 失败处理:如果有任何一个 promise 失败,立即调用 reject,并终止整个操作。

这个实现完全模仿了 Promise.all 的行为。