这一次,我真的会手写Promise了

597 阅读5分钟

前言

之前发过一篇Promise的手写文章,但隔了一段时间之后发现自己又忘了怎么写,于是下决心重新梳理,这次我真的会写了。

Promise做了什么:面向对象写回调

将执行任务和设置回调相分离

Promise通过对象方法,将执行任务和挂载回调的代码相分离,解决了回调嵌套的问题。

  1. new Promise():立即执行任务并激活回调(激活回调是将回调放入微任务队列,实现异步特性)
  2. Promise.then():实现回调挂载
const p = new Promise((resolve, reject) => {
  console.log('task'); // 执行任务
  resolve('data'); // 激活回调
});

p.then((v) => console.log(v)); // 挂载回调

既然如此,对象中就需要两个属性:

  1. 执行结果:传递给回调函数
  2. 回调函数:记录挂载的回调

区分正常处理回调和异常处理回调

传统的回调函数中,经常会在第一个入参传递一个e表示error,比如function(e, data){},我们得自己判断e是否为空来确定执行正常回调还是异常回调。

Promise把这一个回调拆成正常处理回调和异常处理回调两个回调,分别挂载。

p.then(
  (v) => console.log(v),
  (e) => console.log(e)
); 

既然如此,对象中还需要一个属性:

  1. 执行状态:记录任务是否成功,以判断执行哪个回调

链式调用

Promise.then()将返回新的Promise,实现链式调用。

p.then((v) => v).then((v) => v);

最简版Promise

根据上面的理解,我们就可以写一个最简版的Promise,跟我念口诀:“三个属性存状态,两调用和两挂载”

  1. 三个属性:status, result, callbacks
  2. 两调用:立即执行函数中的resolve和reject
  3. 两挂载:实例方法then和catch
class Promise {
  constructor(executor) {
    // 三个属性
    this.status = 'pending'; // 状态
    this.result = null; // 结果
    this.callbacks = []; // 回调

    // 两调用:resolve
    const resolve = (data) => {
      // 修改状态
      this.status = 'fulfilled';
      // 修改结果
      this.result = data;
      // 激活回调
      this.callbacks.forEach((cb) => {
        cb.onResolved();
      });
    };
    
    // 两调用:reject
    const reject = (error) => {
      // 修改状态
      this.status = 'rejected';
      // 修改结果
      this.result = error;
      // 激活回调
      this.callbacks.forEach((cb) => {
        cb.onRejected();
      });
    };

    // 立即执行函数
    executor(resolve, reject);
  }

  // 两挂载:then
  then(onResolved, onRejected) {
    // Promise.then返回Promise
    return new Promise((resolve, reject) => {
    
      // 回调函数封装:把任务放入微任务队列
      const callback = (func) => {
        setTimeout(() => {
          try {
            const result = func(this.result);
            resolve(result);
          } catch (error) {
            reject(error);
          }
        });
      };
      
      // 回调函数生成
      const wrapper = (func) => {
        return () => {
          callback(func);
        };
      };
      
      // 挂载回调函数
      this.callbacks.push({
        onResolved: wrapper(onResolved),
        onRejected: wrapper(onRejected),
      });
    });
  }
   
  // 两挂载:catch
  catch(onRejected) {
    // then的快捷方式
    return this.then(undefined, onRejected);
  }
}

添加防御代码

最简版Promise有很多不完善的地方,接下来就需要对各处漏洞添加防御代码。

Promise状态只能修改一次

  constructor(executor) {
    // ...
    
    const resolve = (data) => {
      // 已修改的状态不能再次修改
      if (this.status !== 'pending') return;
      // ...
    };

    const reject = (error) => {
      // 已修改的状态不能再次修改
      if (this.status !== 'pending') return;
      // ...
    };
    
    // ...
  }

resolve中data如果是Promise需要等待执行结果

  constructor(executor) {
    // ...
    
    const resolve = (data) => {
      // 如果data是Promise,则等待执行结果
      if (data instanceof Promise) {
        data.then(resolve, reject);
      } else {
        // 修改状态
        this.status = "fulfilled";
        // 修改结果
        this.result = data;
        // 激活回调
        this.callbacks.forEach((cb) => {
          cb.onResolved();
        });
      }
    };
    
    // ...
  }

立即执行函数如果异常需要reject

 constructor(executor) {
   //...
   
   // 立即执行函数
    try {
      executor(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }

then需要判断入参是否是函数


  then(onResolved, onRejected) {
    // 判断入参是否为函数(防御代码)
    if (typeof onResolved !== 'function') {
      onResolved = (value) => value;
    }
    if (typeof onRejected !== 'function') {
      onRejected = (error) => {
        throw error;
      };
    }
    
    // ...
  }

then回调任务如果返回Promise需要等待结果

  const callback = (func) => {
    setTimeout(() => {
      try {
        const result = func(this.result);
        // 如果回调返回Promise则等待结果
        if (result instanceof Promise) {
          result.then(
            (value) => resolve(value),
            (error) => reject(error)
          );
        } else {
          resolve(result);
        }
      } catch (error) {
        reject(error);
      }
    });
  };

如果Promise状态已经变化,则then中立刻将回调加入微任务队列


then(onResolved, onRejected) {
    // ...
    
    // 返回Promise
    return new Promise((resolve, reject) => {
      // ...

      // 如果Promise状态已经变化,则then中立刻将回调加入微任务队列
      if (this.status === 'fulfilled') {
        setTimeout(() => {
          callback(onResolved);
        });
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          callback(onRejected);
        });
      }
      if (this.status === 'pending') {
        // 挂载回调
        this.callbacks.push({
          onResolved: wrapper(onResolved),
          onRejected: wrapper(onRejected),
        });
      }
    });
  }

四个静态方法

Promise中还有四个静态方法,都用来创建Promise,但是Promise结果的判断规则不同。

  static resolve(data) {}
  static reject(data) {}
  static all(promises) {}
  static race(promises) {}

resolve

首先是resolve,这个方法用于将数据包装为Promise,如果传入的数据是Promise则直接返回,否则返回一个将入参作为结果的成功状态的Promise。

  static resolve(data) {
    // 如果入参是Promise则直接返回
    if (data instanceof Promise) {
      return data;
      // 如果入参不是Promise则包装为Promise
    } else {
      return new Promise((resolve, reject) => {
        resolve(data);
      });
    }
  }

reject

再来是reject,该方法不论入参是不是Promise,均返回一个失败的Promise,并且值就是传入的数据。

  static reject(data) {
    return new Promise((resolve, reject) => {
      reject(data);
    });
  }

all

接下来是all,all方法用于判断多个promise是否均为成功,如果多个promise都成功则返回所有传入promise的返回值数组;如果有一个promise失败,则返回该失败的promise的返回值。

static all(promises) {
    return new Promise((resolve, reject) => {
      let res = [];
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(
          (value) => {
            // 要保证返回的结果和传入的Promise位置一一对应
            res[i] = value;
            if (res.length === promises.length) {
              resolve(res);
            }
          },
          (error) => {
            reject(error);
          }
        );
      }
    });
  }

race

最后是race,该方法用于多个promise竞争执行,如果其中一个promise成功,则返回该成功promise的返回值;如果其中一个promise失败,则返回该失败promise的返回值。

static race(promises) {
    return new Promise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(
          (value) => {
            resolve(value);
          },
          (error) => {
            reject(error);
          }
        );
      }
    });
  }

尾声

Promise的核心就是利用面向对象将“调用回调和挂载回调”的代码分离,理解了这点就会写了。