请用 JavaScript 实现一个简单的 Promise (符合 Promise/A+ 规范)。

Promise:一场关于异步的优雅冒险

嗨,各位探险家们!今天我们来聊聊 Promise,这玩意儿就像异步世界里的罗盘,能指引我们穿越回调地狱,最终到达优雅编程的彼岸。

想象一下,你要去一个遥远的国度,交通工具是飞鸽传书。你写好信,绑在鸽子腿上,然后就只能等着,不知道鸽子啥时候到,也不知道那边回信啥时候来。这就是异步操作,而 Promise 就是帮你管理这些鸽子的“信鸽调度中心”。

什么是 Promise?

Promise 本质上是一个对象,代表一个异步操作的最终完成 (或失败) 及其结果值。它有三种状态:

  • Pending (等待中): 鸽子还没飞到,或者对方还没回信。这是 Promise 的初始状态。
  • Fulfilled (已成功): 鸽子安全抵达,并且带回了成功的消息。Promise 已经完成,并且有一个结果值。
  • Rejected (已失败): 鸽子迷路了,或者被老鹰叼走了,或者对方回信说“滚”。Promise 已经失败,并且有一个失败原因。

Promise/A+ 规范:Promise 的“交通规则”

Promise 很好用,但如果每个人都用自己的方式“养鸽子”,那异步世界就乱套了。所以,Promise/A+ 规范就诞生了,它定义了 Promise 应该如何工作,就像交通规则一样,保证大家的行为一致。

我们今天的目标,就是用 JavaScript 实现一个符合 Promise/A+ 规范的“信鸽调度中心”。

实现一个简单的 Promise

首先,我们定义一个 MyPromise 类:

class MyPromise {
  constructor(executor) {
    this.state = 'pending'; // 初始状态
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    this.onResolvedCallbacks = []; // 成功的回调函数队列
    this.onRejectedCallbacks = []; // 失败的回调函数队列

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn => fn()); // 执行成功的回调函数
      }
    };

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

    try {
      executor(resolve, reject); // 立即执行 executor 函数
    } catch (error) {
      reject(error); // 如果 executor 抛出错误,则 reject
    }
  }

  then(onFulfilled, onRejected) {
    // 处理 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') {
        // 异步执行,保证在 promise2 构造函数执行完之后再执行
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'rejected') {
        // 异步执行,保证在 promise2 构造函数执行完之后再执行
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        // 如果状态还是 pending,说明异步操作还没有完成,先把回调函数保存起来
        this.onResolvedCallbacks.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;
  }

  catch(onRejected) {
    return this.then(null, onRejected);
  }

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

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

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

  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 => { // 确保是 Promise 对象
          result[i] = value;
          count++;
          if (count === promises.length) {
            resolve(result);
          }
        }, reason => {
          reject(reason);
        });
      }
    });
  }

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

      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        MyPromise.resolve(promise).then(value => {
          resolve(value);
        }, reason => {
          reject(reason);
        });
      }
    });
  }
}

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; // 尝试获取 then 方法
      if (typeof then === 'function') {
        // 如果 x 是一个 Promise,则递归解析
        then.call(x, (y) => {
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject); // 递归解析 y
        }, (r) => {
          if (called) return;
          called = true;
          reject(r);
        });
      } else {
        // 如果 x 不是一个 Promise,则直接 resolve
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      called = true;
      reject(error);
    }
  } else {
    // 如果 x 是一个普通值,则直接 resolve
    resolve(x);
  }
}

// 为了方便测试,我们可以将 MyPromise 暴露出去
MyPromise.deferred = function() {
  let dfd = {};
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}

module.exports = MyPromise;

让我们逐步分解这段代码:

  • constructor(executor) 这是 Promise 的构造函数。executor 是一个函数,它接收两个参数:resolverejectresolve 用于将 Promise 状态变为 fulfilledreject 用于将 Promise 状态变为 rejected

  • statevaluereason 这三个属性分别用于存储 Promise 的状态、成功的值和失败的原因。

  • onResolvedCallbacksonRejectedCallbacks 这两个数组用于存储成功和失败的回调函数。当 Promise 的状态变为 fulfilledrejected 时,我们会依次执行这些回调函数。

  • resolve(value) 这个函数用于将 Promise 的状态变为 fulfilled,并将成功的值存储到 value 属性中。它还会遍历 onResolvedCallbacks 数组,依次执行其中的回调函数。

  • reject(reason) 这个函数用于将 Promise 的状态变为 rejected,并将失败的原因存储到 reason 属性中。它还会遍历 onRejectedCallbacks 数组,依次执行其中的回调函数。

  • then(onFulfilled, onRejected) 这是 Promise 最重要的一个方法。它接收两个参数:onFulfilledonRejected,分别用于处理 Promise 成功和失败的情况。它会返回一个新的 Promise 对象,这个新的 Promise 对象的 resolve 或 reject 取决于 onFulfilledonRejected 的返回值。

  • resolvePromise(promise2, x, resolve, reject) 这个函数用于处理 then 方法返回的 Promise 对象。它会判断 x 的类型,如果 x 是一个 Promise 对象,则递归调用 resolvePromise 函数,直到 x 不是一个 Promise 对象为止。如果 x 是一个普通值,则直接 resolve promise2

  • catch(onRejected) 相当于 then(null, onRejected),用于捕获 Promise 的错误。

  • finally(callback) 无论 Promise 成功还是失败,都会执行 callback 函数。

  • static resolve(value) 用于创建一个状态为 fulfilled 的 Promise 对象。

  • static reject(reason) 用于创建一个状态为 rejected 的 Promise 对象。

  • static all(promises) 接收一个 Promise 数组,当所有 Promise 都成功时,返回一个包含所有 Promise 结果的数组。如果其中一个 Promise 失败,则返回一个 rejected 的 Promise。

  • static race(promises) 接收一个 Promise 数组,返回第一个 resolve 或 reject 的 Promise。

关键点和难点

  • 异步执行: resolvereject 函数需要在异步操作完成后才能执行。为了保证在 Promise 构造函数执行完之后再执行回调函数,我们需要使用 setTimeoutprocess.nextTick 等方法将回调函数放入事件循环队列中。

  • resolvePromise 函数: 这是实现 Promise/A+ 规范的关键。它需要处理各种情况,包括:

    • x 是一个 Promise 对象
    • x 是一个 thenable 对象
    • x 是一个普通值
    • 循环引用
  • 状态的不可逆性: Promise 的状态一旦变为 fulfilledrejected,就不能再改变。

Promise/A+ 规范验证

为了验证我们的 MyPromise 是否符合 Promise/A+ 规范,我们可以使用 promises-aplus-tests 这个测试工具。

  1. 首先,安装 promises-aplus-tests

    npm install -g promises-aplus-tests
  2. 然后,创建一个测试文件 test.js,内容如下:

    const MyPromise = require('./MyPromise');
    
    MyPromise.deferred = MyPromise.deferred || function () {
        let resolve, reject;
        let promise = new MyPromise(function (_resolve, _reject) {
            resolve = _resolve;
            reject = _reject;
        });
        return {
            promise: promise,
            resolve: resolve,
            reject: reject
        };
    };
    
    module.exports = MyPromise;
  3. 最后,运行测试:

    promises-aplus-tests test.js

如果所有的测试都通过了,那么恭喜你,你的 MyPromise 已经符合 Promise/A+ 规范了!

总结

我们用 JavaScript 实现了一个简单的 Promise,并且验证了它符合 Promise/A+ 规范。虽然这只是一个简单的实现,但它包含了 Promise 的核心概念和原理。

特性 描述
状态 Pending, Fulfilled, Rejected
then 方法 接收 onFulfilledonRejected 回调函数,返回一个新的 Promise
resolvePromise 处理 then 方法返回的 Promise 对象,递归解析 Promise
静态方法 resolve, reject, all, race
错误处理 使用 try...catch 捕获 executor 函数和回调函数中的错误
异步执行 使用 setTimeoutprocess.nextTick 将回调函数放入事件循环队列中,保证在 Promise 构造函数执行完之后再执行回调函数

通过这次“信鸽调度中心”的冒险,相信你对 Promise 有了更深入的理解。希望你能将 Promise 应用到你的实际项目中,让异步编程变得更加优雅和高效!

现在,拿起你的罗盘,开始你的异步编程之旅吧!

发表回复

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