封装异步操作:自定义 Promise 类实现

好的,各位编程界的弄潮儿,欢迎来到老码农的异步世界!今天咱们不聊风花雪月,专攻一门武艺:封装异步操作,打造专属 Promise 类! 🚀

想象一下,你的代码就像一位杂耍艺人,手里同时抛着N个任务。同步代码就像他一次只能抛一个球,必须等一个落地才能抛下一个,效率那个叫一个惨不忍睹!而异步代码,就像他能同时抛N个球,还能优雅地接住每一个,丝滑流畅!

Promise,就是让这位杂耍艺人更加游刃有余的秘诀。它就像一个承诺,承诺将来会给你一个结果,不管成功还是失败,都会给你一个交代,绝不让你苦苦等待,望眼欲穿。

但是!别人家的 Promise 终究是别人家的,用起来总觉得不够贴心。今天,老码农就带大家撸起袖子,打造一个属于自己的 Promise 类,让异步操作从此如臂使指,掌控全局!💪

一、Promise 的前世今生:扒一扒它的底裤

在咱们动手之前,先来了解一下 Promise 到底是个什么玩意儿。别怕,老码农保证不讲晦涩的概念,只用大白话解释:

  • Promise 是一个对象:没错,它就是个对象,一个代表着未来某个不确定结果的对象。你可以把它想象成一个“欠条”,上面写着“将来给你一个值”。

  • 它有三种状态

    • Pending (等待中): 就像欠条刚开出来,啥也没发生,未来充满变数。
    • Resolved (已解决): 欠条兑现了,你拿到钱了,任务成功完成了!
    • Rejected (已拒绝): 欠条作废了,钱没了,任务失败了!
  • 状态只能改变一次: 一个 Promise 对象,一旦从 Pending 变成了 Resolved 或 Rejected,就再也回不去了,就像泼出去的水,覆水难收。

  • 它自带 then 和 catch 方法

    • then() 方法:用来处理 Resolved 状态,也就是成功的情况。
    • catch() 方法:用来处理 Rejected 状态,也就是失败的情况。

二、手撸 Promise:从零开始造轮子

好了,理论知识武装完毕,咱们正式开始造轮子!

  1. 搭建基本框架
class MyPromise {
  constructor(executor) {
    // executor 是一个函数,它接受 resolve 和 reject 两个参数
    this.state = 'pending'; // 初始状态为 pending
    this.value = undefined; // 成功的值
    this.reason = undefined; // 失败的原因
    this.onResolvedCallbacks = []; // 存储成功的回调
    this.onRejectedCallbacks = []; // 存储失败的回调

    const resolve = (value) => {
      if (this.state === 'pending') {
        this.state = 'resolved';
        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(onResolved, onRejected) {
      // TODO: 实现 then 方法
  }

  catch(onRejected) {
      // TODO: 实现 catch 方法
  }
}

这段代码定义了一个 MyPromise 类,它接受一个 executor 函数作为参数。这个 executor 函数会立即执行,并且接收两个参数:resolvereject。这两个参数都是函数,用来改变 Promise 的状态。

  • resolve(value):将 Promise 的状态变为 resolved,并且将 value 作为成功的值。
  • reject(reason):将 Promise 的状态变为 rejected,并且将 reason 作为失败的原因。
  1. 实现 then 方法

then 方法是 Promise 的核心,它用来处理成功和失败的情况。

then(onResolved, onRejected) {
    // 处理 onResolved 和 onRejected 不是函数的情况
    onResolved = typeof onResolved === 'function' ? onResolved : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === 'resolved') {
        // 为了拿到 promise2,这里需要用 setTimeout 模拟异步
        setTimeout(() => {
          try {
            const x = onResolved(this.value);
            resolvePromise(promise2, x, resolve, reject); // 处理返回值
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject); // 处理返回值
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.state === 'pending') {
        // 如果是 pending 状态,先把回调函数存起来
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onResolved(this.value);
              resolvePromise(promise2, x, resolve, reject); // 处理返回值
            } catch (e) {
              reject(e);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject); // 处理返回值
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });

    return promise2; // 返回一个新的 Promise
  }

这个 then 方法做了以下几件事:

  • 处理参数:如果 onResolvedonRejected 不是函数,就给它们一个默认值,保证它们总是可调用的。
  • 创建新的 Promise:每次调用 then 方法,都会返回一个新的 Promise 对象 (promise2)。
  • 处理不同的状态
    • 如果当前 Promise 的状态是 resolved,就执行 onResolved 函数,并且将结果传递给 resolvePromise 函数处理。
    • 如果当前 Promise 的状态是 rejected,就执行 onRejected 函数,并且将结果传递给 resolvePromise 函数处理。
    • 如果当前 Promise 的状态是 pending,就把 onResolvedonRejected 函数存起来,等到状态改变的时候再执行。
  • 异步执行:为了保证 onResolvedonRejected 函数是异步执行的,这里使用了 setTimeout
  1. 实现 catch 方法

catch 方法是 then 方法的语法糖,它只用来处理失败的情况。

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

catch 方法实际上就是调用 then 方法,并且将 onRejected 作为第二个参数传递进去。

  1. 实现 resolvePromise 函数

resolvePromise 函数是用来处理 then 方法返回值的核心逻辑。它的作用是:

  • 判断返回值 x 的类型。
  • 如果 x 是一个 Promise 对象,就递归调用 resolvePromise 函数,直到 x 不是 Promise 对象为止。
  • 如果 x 是一个普通值,就将 promise2 的状态变为 resolved,并且将 x 作为成功的值。
  • 如果 x 抛出了一个错误,就将 promise2 的状态变为 rejected,并且将错误作为失败的原因。
function resolvePromise(promise2, x, resolve, reject) {
  // 防止循环引用
  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }

  let called; // 避免多次调用 resolve 或 reject

  if (x instanceof MyPromise) {
    // 如果 x 是一个 Promise 对象,递归调用 resolvePromise
    if (x.state === 'pending') {
      // 如果 x 的状态还是 pending,就等待 x 的状态改变
      x.then(
        y => {
          resolvePromise(promise2, y, resolve, reject);
        },
        reject
      );
    } else {
      x.then(resolve, reject); // 直接调用 x 的 then 方法
    }
  } else if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    // 如果 x 是一个对象或函数
    try {
      const then = x.then; // 取出 x 的 then 方法

      if (typeof then === 'function') {
        // 如果 then 是一个函数,就认为 x 是一个 thenable 对象
        then.call(
          x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } else {
        // 如果 then 不是一个函数,就将 x 作为普通值 resolve
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // 如果 x 是一个普通值,就将 x 作为普通值 resolve
    resolve(x);
  }
}
  1. 添加静态方法

Promise 还有一些静态方法,比如 resolverejectall

  • MyPromise.resolve(value):创建一个状态为 resolved 的 Promise 对象。
MyPromise.resolve = function(value) {
  return new MyPromise((resolve) => {
    resolve(value);
  });
};
  • MyPromise.reject(reason):创建一个状态为 rejected 的 Promise 对象。
MyPromise.reject = function(reason) {
  return new MyPromise((resolve, reject) => {
    reject(reason);
  });
};
  • MyPromise.all(promises):接收一个 Promise 数组,当所有 Promise 都 resolved 时,返回一个新的 Promise,并且将所有 Promise 的结果放到一个数组中;如果其中一个 Promise rejected,就返回一个新的 Promise,并且将 rejected 的原因作为失败的原因。
MyPromise.all = function(promises) {
  return new MyPromise((resolve, reject) => {
    const result = [];
    let count = 0;

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

    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);
          }
        },
        reject
      );
    }
  });
};
  • MyPromise.race(promises):接收一个 Promise 数组,返回一个新的 Promise,它的状态和第一个改变状态的 Promise 相同。
MyPromise.race = function(promises) {
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      const promise = promises[i];

      MyPromise.resolve(promise).then(
        resolve,
        reject
      );
    }
  });
};

三、测试你的 Promise:是骡子是马拉出来溜溜

辛辛苦苦撸了这么多代码,是时候检验一下成果了!

const promise = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功了!');
    // reject('失败了!');
  }, 1000);
});

promise
  .then(
    value => {
      console.log('resolve:', value);
      return 'then 的返回值';
    },
    reason => {
      console.log('reject:', reason);
    }
  )
  .then(value => {
    console.log('第二个 then:', value);
  })
  .catch(reason => {
    console.log('catch:', reason);
  });

运行这段代码,看看控制台的输出是否符合预期。如果一切顺利,恭喜你,你已经成功打造了一个属于自己的 Promise 类!🎉

四、进阶之路:让你的 Promise 更上一层楼

当然,咱们的 Promise 还有很大的提升空间。

  • 支持 Promise Chaining: 完善 then 方法的返回值处理,实现链式调用。
  • 实现 finally 方法: 无论 Promise 的状态是 resolved 还是 rejected,finally 方法都会执行。
  • 添加类型检查: 使用 TypeScript 等工具,为 Promise 添加类型检查,提高代码的健壮性。
  • 优化性能: 深入了解 Promise 的内部实现,优化代码,提升性能。

五、总结:掌握 Promise,掌控未来

Promise 是异步编程的利器,掌握它可以让你更好地处理复杂的异步操作。通过手撸 Promise,你可以更深入地了解它的内部机制,并且打造一个更符合自己需求的 Promise 类。

希望今天的分享能帮助你打开异步世界的大门,让你的代码更加优雅、高效!记住,编程的道路永无止境,让我们一起努力,不断学习,不断进步!💪

最后,送给大家一句老码农的座右铭:“代码虐我千百遍,我待代码如初恋!” 😉

发表回复

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