采用 TDD 的方式写个 Promise

779 阅读20分钟

时至今日,我想大家对 Promise 肯定是不陌生了,它解决的是异步编码风格的问题。

而手撸一个 Promise 往往成为面试中的常客,笔者刚毕业时也看过一些文章尝试过手写一个 MyPromise,发现写不出来,看着别人写的也是一脸懵逼。随着工作经验的增加,慢慢地发现手写个 MyPromise 其实也没那么难。

本文将会采用 TDD(测试驱动开发) 方式,通过从简到繁介绍原生 Promise 语法和书写测试用例。然后根据语法和测试用例来手写 MyPromise。这种循序渐进的方式,不需要你一开始就对 Promise 语法了如指掌,反而通过本文,不仅可以熟悉掌握 Promise 的所有语法,还可以自己写一个 MyPromise,从使用到底层原理彻底搞定 Promise。

希望本文对你有益,如果错误,欢迎指正。

前言

在开始之前,我想你应该具备两个知识点:

  • 对 Promise 的概念和使用有个基本的了解,推荐先看看阮一峰老师的文章:ES6 入门教程。不用全部看完,有个基本了解就足够了。
  • 大概了解下浏览器的事件循环机制,Promise 回调函数的执行时机是微任务事件。

接下来,我们正式开始。

基本用法

我们先看下 Promise 的基本用法。

new Promise((resolve, reject) => {
  console.log('参数函数执行');
  resolve('resolve');
}).then(res => {
  console.log('then', res);
})
console.log('end');

运行结果如下:

image.png

Promise 对象是一个构造函数,用来生成 Promise 实例。构造函数接受一个函数作为参数,该函数有两个参数分别是 resolve 和 reject,它们两个都是函数,由 Promise 内部提供。

基本语法有:

  • Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败),一旦状态改变,就会确定下来,不会再变更了,任何时候都可以得到这个结果。
  • 构造函数接收的函数参数会立即被调用执行。
  • 调用 resolve 函数,状态则会从 pending 转为 fulfilled,并调用 onFulfilled 回调函数(then 方法第一个参数),入参为异步执行结果。
  • 调用 reject 函数,状态则会从 pending 转为 rejected,并调用 onRejected 回调函数(then 方法第二个参数或者 catch 方法第一个参数),入参为错误信息。
  • onFulfilledonRejected 回调函数的执行时机是异步的,并且是微任务事件。

基于上面的基本语法,我们就可以很好理解上述例子的输出结果。参数函数会立即执行,并将 resolvereject 函数作为入参,所以第一行打印了 参数函数执行,然后调用 resolve 函数,Promise 状态转为 fulfilled,并调用 onFulfilled 回调函数,由于是微任务事件,需要等同步代码执行完成后再执行。所以同步语句 console.log('end') 先执行,onFulfilled 回调再执行,并打印出 then resolve

接下来,我们就开始写我们的 MyPromise。基本的结构是这样的:

  • 首先是基本的校验。
  • 然后需要提供 resolve 和 reject 方法。
  • 接下来,需要同步立即执行参数函数,并将 resolve 和 reject 方法作为参数传入。
"use strict"

// 判断是否为函数的工具方法
const isFunction = fun => typeof fun === 'function';

// 状态常量定义
const PENDING = 'pending';
const FULFILLED ='fulfilled';
const REJECTED = 'rejected';

function MyPromise(handle) {
    // 调用方式校验,只能 new,不能直接调用
    if (!(this instanceof MyPromise)) {
        throw new Error('MyPromise is a constructor, and cannot be called directly');
    }
    // 参数校验,必须为函数
    if (!isFunction(handle)) {
        throw new Error('MyPromise must accept a function as parameter');
    }

    // 内部变量定义
    let _result = undefined; // 异步执行结果
    let _status = PENDING; // 当前 MyPromise 状态 pending、fulfilled、rejected

    // 定义 resolve 函数
    const resolve = () => {
        // TODO
    }

    // 定义 reject 函数
    const reject = () => {
        // TODO
    }

    // 参数函数立即执行,并将 resolve 和 reject 函数作为参数传入
    // 使用 call 确保 handle 内的 this 指向不变
    handle.call(null, resolve, reject);
}

测试用例 1:参数函数同步执行

上面的基本结构就可以通过这个测试,我们来验证下:

// 测试用例 1
new Promise(() => console.log('Promise 参数函数执行'));
console.log('Promise end');

new MyPromise(() => console.log('MyPromise 参数函数执行'));
console.log('MyPromise end');

测试通过。

测试用例 2:调用 resolve ,状态为 fulfilled,调用 onFulfilled

onFulfilled 回调函数,是通过 then 方法来注册的(第一个参数),所以接下来,我们需要先实现 then 方法,核心功能点有:

  • 可以多次调用 then 方法注册回调。状态变更后,按照注册顺序依次执行。
  • 第一个参数为 onFulfilled 回调函数,第二个参数为 onReject 回调函数,都是可选的。(如果不是函数,则会被忽略)。

先写测试用例:

// 测试用例 2
const p1 = new Promise((resolve) => resolve('异步执行结果'));
p1.then(r => console.log('Promise then 1', r));
p1.then(r => console.log('Promise then 2', r));

const p2 = new MyPromise((resolve) => resolve('异步执行结果'));
p2.then(r => console.log('MyPromise then 1', r));
p2.then(r => console.log('MyPromise then 2', r));

Promise 执行结果如下:

image.png

接下来,我们继续完善功能。

  • 增加一个变量 _callbacks 来记录回调函数。
  • 新增实例对象的 then 方法,注册回调函数。
  • 完善 resolve 函数功能,状态为 pending 时, 转换为 fulfilled,并依次调用 onFulfilled 回调函数(异步并以微任务形式调用),状态不是 pending 时,不能执行,直接返回。
// 内部变量定义
const _callbacks = []; // 子项为对象,记录 onFulfilled, onRejected。

// 执行 resolve 逻辑
const handleResolve = (cb) => {
    const { onFulfilled } = cb;
    isFunction(onFulfilled) && onFulfilled.call(null, _result);
}

// 根据当前状态执行 resolve 或者 reject 回调
const handleCB = (cb) => {
    if (_status === FULFILLED) {
        handleResolve(cb);
    }
}

// 定义实例对象上的 then 方法
this.then = (onFulfilled, onRejected) => {
    const cb = {
        onFulfilled,
        onRejected
    };
    // 注册对应的回调方法
    _callbacks.push(cb);
}

// 定义 resolve 函数
const resolve = (result) => {
    const run = () => {
        // 如果状态已经改变了,不会再执行了
        if (_status !== PENDING) {
            return;
        }

        // 状态变更为 fulfilled
        _status = FULFILLED;

        // 记录执行结果
        _result = result;

        // 依次调用 onFulfilled 回调
        _callbacks.forEach(cb => handleCB(cb));
    }
    // 异步以微任务形式执行(借助原生 Promise 来实现微任务)
    Promise.resolve().then(run);
}

MyPromise 执行测试用例:

image.png

运行结果和 Promise,测试通过。

测试用例 3:调用 reject ,状态为 rejected,调用 onRejected

onRejected 回调函数有两种注册方法:一种是通过 then 方法第二个参数来注册;还有一种是实例对象 catch 方法第一个参数。

先写测试用例:

// 测试用例 3
const p1 = new Promise((resolve, reject) => reject('有错误'));
p1.then(null, r => console.log('Promise reject 1', r));
p1.then(null, r => console.log('Promise reject 2', r));
p1.catch(r => console.log('Promise catch 1', r));
p1.catch(r => console.log('Promise catch 2', r));

const p2 = new MyPromise((resolve, reject) => reject('有错误'));
p2.then(null, r => console.log('MyPromise reject 1', r));
p2.then(null, r => console.log('MyPromise reject 2', r));
p2.catch(r => console.log('MyPromise catch 1', r));
p2.catch(r => console.log('MyPromise catch 2', r));

Promise 执行结果如下:

image.png

接下来,继续完善功能:

  • 添加 catch 方法,catch 方法可以看着是 then 方法的一种简写,可以直接使用 then 方法来实现。
  • 完善 reject 函数功能,状态为 pending 时, 转换为 rejected,并依次调用 onRejected 回调函数(异步并以微任务形式调用),状态不是 pending 时,不能执行,直接返回。
  • handleCB 方法添加 rejected 判断逻辑。
// 执行 reject 逻辑
const handleReject = (cb) => {
    const { onRejected } = cb;
    isFunction(onRejected) && onRejected.call(null, _result);
}

// 根据当前状态执行 resolve 或者 reject 回调
const handleCB = (cb) => {
    if (_status === FULFILLED) {
        handleResolve(cb);
    }

    if (_status === REJECTED) {
        handleReject(cb);
    }
}

// 定义实例对象上的 catch 方法
this.catch = (onRejected) => {
    return this.then(null, onRejected);
}

// 定义 reject 函数
const reject = (error) => {
    const run = () => {
        // 如果状态已经改变了,不会再执行了
        if (_status !== PENDING) {
            return;
        }

        // 状态变更为 rejected
        _status = REJECTED;

        // 记录执行结果
        _result = error;

        // 依次调用 onRejected 回调
        _callbacks.forEach(cb => handleCB(cb));
    }

    // 异步以微任务形式执行(借助原生 Promise 来实现微任务)
    Promise.resolve().then(run);
}

MyPromise 执行测试用例:

image.png

运行结果和 Promise,测试通过。

测试用例 4:异步注册回调函数也能执行

上面的测试用例中,我们是同步使用 then 或者 catch 来注册回调函数的,能保证在 onResolved 在以微任务调用时,回调队列是不为空的。

Promise 是支持异步注册回调的,也能保证回调函数以微任务时机被调用。

我们先来看测试用例:

// 测试用例 4
const p1 = new Promise((resolve) => resolve('异步执行结果'));
// setTimeout 宏任务,在 resolve 之后执行
setTimeout(() => {
  p1.then(r => console.log('Promise then', r));
  console.log('Promise end');
}, 0);

const p2 = new MyPromise((resolve) => resolve('异步执行结果'));
setTimeout(() => {
  p2.then(r => console.log('MyPromise then', r));
  console.log('MyPromise end');
}, 0);

Promise 执行结果如下:

image.png

根据执行结果,我们可以得到,Promise 状态确定后注册的回调函数会直接执行,由于先打印 end,所以是异步以微任务事件调用。

我们继续完善 MyPromise。在 then 方法中,我们需要根据当前的状态,执行不同的操作。

// 定义实例对象上的 then 方法
this.then = (onFulfilled, onRejected) => {
    // 注册对应的回调方法
    const cb = {
        onFulfilled,
        onRejected
    };

    // 状态是 pending 注册对应的回调方法。
    if (_status === PENDING) {
        _callbacks.push(cb);
        return;
    }

    // 状态已确定,异步微任务执行回调
    Promise.resolve().then(() => handleCB(cb));
}

MyPromise 执行测试用例:

image.png

运行结果和 Promise,测试通过。

测试用例 5:链式调用

我们知道,Promise 是支持链式调用的,也就是 then 返回的是一个新的 Promise 实例对象,而这个新的 Promise 对象的状态是由当前 Promise 对象状态决定的。

我们先来看测试用例:

new Promise((resolve) => resolve('异步执行结果')).then(r => {
  console.log('Promise then 1 =>', r);
  return 'then 1 result'
}).then(r => console.log('Promise then 2 =>', r))

new MyPromise((resolve) => resolve('异步执行结果')).then(r => {
    console.log('MyPromise then 1 =>', r);
    return 'then 1 result'
}).then(r => console.log('MyPromise then 2 =>', r))

Promise 执行结果如下:

image.png

如果当前 Promise 状态转为 fulfilled,则会调用返回值 Promise 的 resolve,入参是异步执行结果,也是当前 onFulfilled 的返回值。

如果当前 Promise 状态转为 rejected,则会调用返回值 Promise 的 reject,入参是异步执行结果,也就是当前 onRejected 的返回值。

接下来,我们继续完善 MyPromise 功能。

首先,then 方法需要返回一个新的 MyPromise 对象,并且需要记录下 nextResolvenextReject,在当前 MyPromise 状态改变时,需要调用对应的 nextResolve 或者 nextReject

// 定义实例对象上的 then 方法
this.then = (onFulfilled, onRejected) => {
    // 返回新的 MyPromise 实例对象
    return new MyPromise((nextResolve, nextReject) => {
        const cb = {
            onFulfilled,
            onRejected,
            nextResolve,
            nextReject
        };

        // 状态是 pending 注册对应的回调方法。
        if (_status === PENDING) {
            _callbacks.push(cb);
            return;
        }

        // 状态已确定,异步微任务执行回调
        Promise.resolve().then(() => handleCB(cb));
    });
}

接下来,我们需要处理下 handleResolve 和 handleReject。

// 执行 resolve 逻辑
const handleResolve = (cb) => {
    const { onFulfilled, nextResolve } = cb;
    // 返回 MyPromise 异步结果为 onFulfilled 返回值,或者当前 MyPromise 结果。
    let nextVal = _result;
    if (isFunction(onFulfilled)) {
        nextVal = onFulfilled.call(null, _result);
    }

    // 触发下一个 MyPromise resolve
    nextResolve(nextVal);
}

// 执行 reject 逻辑

const handleReject = (cb) => {
    const { onRejected, nextReject } = cb;
    // 返回 MyPromise 异步结果为 onRejected 返回值,或者当前 MyPromise 结果。
    let nextVal = _result;
    if (isFunction(onRejected)) {
        nextVal = onRejected.call(null, _result);
    }

    // 触发下一个 MyPromise reject
    nextReject(nextVal);
}

MyPromise 执行测试用例:

image.png

运行结果和 Promise,测试通过。

测试用例 6:错误捕获、传递和拦截

对于 Promise,当调用 reject 或者执行代码抛出代码,Promise 能够捕获到该错误,调用 onRejected 回调,如果没有注册该回调,该错误会一直往后传递,直到被一个 onRejected 回调处理,一旦处理了就不会再传递了。

就算没有 catch 错误,Promise 内部的错误也不会影响外层环境。

我们先看测试用例:

new Promise((resolve) => resolve())
.then(r => {
  throw new Error('Promise Error');
})
.then(r => console.log(r)).catch(r => console.log('Promise catch 1', r))
.catch(r => console.log('Promise catch 2', r))

new MyPromise((resolve) => resolve())
.then(r => {
  throw new Error('MyPromise Error');
})
.then(r => console.log(r)).catch(r => console.log('MyPromise catch 1', r))
.catch(r => console.log('MyPromise catch 2', r))

Promise 执行结果如下:

image.png

从上面例子可以看出,当 Promise 抛出错误后,会一直向后传递,直到被处理。

我们继续完善 MyPromise。首先在 handleResolvehandleReject 里我们需要添加 try/catch 来捕获执行时错误。

// 执行 resolve 逻辑
const handleResolve = (cb) => {
  const { onFulfilled, nextResolve, nextReject } = cb;
  try {
    // 返回 MyPromise 异步结果为 onFulfilled 返回值,或者当前 MyPromise 结果。
    let nextVal = _result;
    if (isFunction(onFulfilled)) {
      nextVal = onFulfilled.call(null, _result);
    }

    // 触发下一个 MyPromise resolve
    nextResolve.call(null, nextVal);          
    
  } catch (e) {
    // 抛出错误,直接 reject
    nextReject.call(null, e);
  }
}

另外,参数函数执行时,也需要添加 try/catch 来捕获执行时错误。

function MyPromise(handle) {
  ....
  try {
    // 参数函数立即执行,并将 resolve 和 reject 函数作为参数传入
    handle.call(null, resolve, reject);
  } catch (e) {
    reject(e);
  }
}

接下来就要实现错误的传递和拦截功能了。我们需要在 handleReject 中添加一些逻辑,也就是在 reject 时,如果有 onRejected 回调,甚好,直接调用处理来错误,错误不再向后传递,也就是错误被拦截了。如果没有 onRejected 回调,就需要错误向后传递,触发下一个 MyPromise reject

// 执行 reject 逻辑
const handleReject = (cb) => {
  const { onRejected, nextReject, nextResolve } = cb;
  try {
    // 返回 MyPromise 异步结果为 onRejected 返回值,或者当前 MyPromise 结果。
    let nextVal = _result;
    
    // 存在 onRejected 回调,错误不在传递,触发下一个 MyPromise resolve
    if (isFunction(onRejected)) {
      nextVal = onRejected.call(null, _result);
    
      // 这里是触发 resolve
      nextResolve.call(null, nextVal);  
    } else { // 没有 onRejected 回调,错误向后传递,触发下一个 MyPromise reject
      // 这里是触发 reject
      nextReject.call(null, nextVal);          
    }
  } catch (e) {
    // 抛出错误,直接 reject
    nextReject.call(null, e);
  }
}

MyPromise 执行测试用例:

运行结果和 Promise 一样,可以看到,执行时的错误能被捕获到,错误能穿过 then 往后传递,一旦被处理,就不会再传递了,测试通过。

测试用例 7:resolve 参数为 Promise 对象

resolve 的参数,也就是异步执行结果也可以是个 Promise 对象,then 返回值的 Promise 对象的状态,需要等该 Promise 对象执行完成,并由它的状态决定。

我们先看看测试用例:

new Promise((resolve, reject) => {
  resolve('resolve');
}).then(res => {
  return new Promise(res => res('Promise return Promise resolve'))
}).then(r => console.log('Promise return Promise then =>', r))

new Promise((resolve, reject) => {
  resolve('resolve');
}).then(res => {
  return new Promise((res, rej) => rej('Promise return Promise reject'))
}).then(r => console.log('Promise return Promise then', r)).catch(r => console.log('Promise return Promise catch =>', r))

Promise 执行结果如下:

image.png

后面的 Promise 需要等返回值的状态转变。最后一个打印 'Promise return promise catch',说明了返回值 Promise 的状态决定了后面 Promise 的状态。

我们来实现下这个功能,只需要在 resolve 判断下即可。

// 定义 resolve 函数
const resolve = (result) => {
  const run = () => {
    // 如果状态已经改变了,不会再执行了
    if (_status !== PENDING) {
      return;
    }

    // 判断 result 是否为 MyPromise
    if (result instanceof MyPromise) {
      // 等 result 状态转变再执行 resolve 或者 reject
      result.then(resolve, reject);
      return;
    }

    // 状态变更为 fulfilled
    _status = FULFILLED;

    // 记录执行结果
    _result = result;

    // 依次调用 onFulfilled 回调
    _callbacks.forEach(cb => handleCB(cb));
  }

  // 异步以微任务形式执行(借助原生 Promise 来实现微任务)
  Promise.resolve().then(run);
}

MyPromise 执行测试用例:

// 测试用例 7
new MyPromise((resolve, reject) => {
  resolve('resolve');
}).then(res => {
  return new MyPromise(res => res('MyPromise return MyPromise resolve'))
}).then(r => console.log('MyPromise return MyPromise then =>', r))

new MyPromise((resolve, reject) => {
  resolve('resolve');
}).then(res => {
  return new MyPromise((res, rej) => rej('MyPromise return MyPromise reject'))
}).then(r => console.log('MyPromise return MyPromise then', r)).catch(r => console.log('MyPromise return MyPromise catch =>', r))

image.png

运行结果和 Promise 一致,测试通过。

测试用例 8:finally,不仅仅是 then 的变形

finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。finally 方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

我们先看看测试用例:

new Promise(resolve => resolve('异步执行结果'))
.finally(() => console.log('Promise finally then'))

new Promise(() => {throw new Error('出错了')})
.finally(() => console.log('Promise catch then'))

Promise 执行结果:

从运行结果我们可以看出,不管是 resolve 还是 rejectfinally 都会执行,而且不接受任何参数(打印 undefined),这和上面介绍的特性是一致的。

不过有一个点需要我们注意,那就是 finally 是不能拦截掉错误的(上述运行结果最终打印出了没有被捕获的错误),所以使用 then 实现 finally 时,需要继续抛出错误。

具体实现如下:

// 定义实例对象上的 finally 方法
this.finally = (onFinally) => {
  return this.then(
    result => Promise.resolve(onFinally()).then(() => result),
    error => Promise.resolve(onFinally()).then(() => { throw error })
  );
}

MyPromise 执行测试用例:

new MyPromise(resolve => resolve('异步执行结果'))
.finally(r => console.log('MyPromise finally then', r))

new MyPromise(() => { throw Error ('出错了')})
.finally(r => console.log('MyPromise finally catch', r))

执行结果与 Promise 一致,测试通过。

完整版

至此,我们就完整地实现了一个 MyPromise,相信你对 Promise 的使用也有了更深的了解,下面是完整的代码。

"use strict"

// 判断是否为函数的工具方法
const isFunction = fun => typeof fun === 'function';

// 状态常量定义
const PENDING = 'pending';
const FULFILLED ='fulfilled';
const REJECTED = 'rejected';

function MyPromise(handle) {
  // 调用方式校验,只能 new,不能直接调用
  if (!(this instanceof MyPromise)) {
    throw new Error('MyPromise is a constructor, and cannot be called directly');
  }

  // 参数校验,必须为函数
  if (!isFunction(handle)) {
    throw new Error('MyPromise must accept a function as parameter');
  }

  // 内部变量定义
  let _result = undefined; // 异步执行结果
  let _status = PENDING; // 当前 MyPromise 状态 pending、fulfilled、rejected
  const _callbacks = []; // 子项为对象,记录 onFulfilled, onRejected。

  // 执行 resolve 逻辑
  const handleResolve = (cb) => {
    const { onFulfilled, nextResolve, nextReject } = cb;
    try {
      // 返回 MyPromise 异步结果为 onFulfilled 返回值,或者当前 MyPromise 结果。
      let nextVal = _result;
      if (isFunction(onFulfilled)) {
        nextVal = onFulfilled.call(null, _result);
      }

      // 触发下一个 MyPromise resolve
      nextResolve.call(null, nextVal);          
      
    } catch (e) {
      // 抛出错误,直接 reject
      nextReject.call(null, e);
    }
  }

  const handleReject = (cb) => {
    const { onRejected, nextReject, nextResolve } = cb;
    try {
      // 返回 MyPromise 异步结果为 onRejected 返回值,或者当前 MyPromise 结果。
      let nextVal = _result;

      // 存在 onRejected 回调,错误不在传递,触发下一个 MyPromise resolve
      if (isFunction(onRejected)) {
        nextVal = onRejected.call(null, _result);
        
        // 这里是触发 resolve
        nextResolve.call(null, nextVal);  

      } else { // 没有 onRejected 回调,错误向后传递,触发下一个 MyPromise reject
        // 这里是触发 reject
        nextReject.call(null, nextVal);          
      }
    } catch (e) {
      // 抛出错误,直接 reject
      nextReject.call(null, e);
    }
  }

  // 根据当前状态执行 resolve 或者 reject 回调
  const handleCB = (cb) => {
    if (_status === FULFILLED) {
      handleResolve(cb);
    }

    if (_status === REJECTED) {
      handleReject(cb);
    }
  }

  // 定义实例对象上的 catch 方法
  this.catch = (onRejected) => {
    return this.then(null, onRejected);
  }

  // 定义实例对象上的 finally 方法
  this.finally = (onFinally) => 
    return this.then(
      result => Promise.resolve(onFinally()).then(() => result),
      error => Promise.resolve(onFinally()).then(() => { throw error })
    );
  }

  // 定义实例对象上的 then 方法
  this.then = (onFulfilled, onRejected) => {
    // 返回新的 MyPromise 实例对象
    return new MyPromise((nextResolve, nextReject) => {
      const cb = {
        onFulfilled,
        onRejected,
        nextResolve,
        nextReject
      };

      // 状态是 pending 注册对应的回调方法。
      if (_status === PENDING) {
        _callbacks.push(cb);
        return;
      }

      // 状态已确定,异步微任务执行回调
      Promise.resolve().then(() => handleCB(cb));
    });
  }

  const resolve = (result) => {
    const run = () => {
      // 如果状态已经改变了,不会再执行了
      if (_status !== PENDING) {
        return;
      }

      // 判断 result 是否为 MyPromise
      if (result instanceof MyPromise) {
        // 等 result 状态转变再执行 resolve 或者 reject
        result.then(resolve, reject);
        return;
      }

      // 状态变更为 fulfilled
      _status = FULFILLED;

      // 记录执行结果
      _result = result;

      // 依次调用 onFulfilled 回调
      _callbacks.forEach(cb => handleCB(cb));
    }

    // 异步以微任务形式执行(借助原生 Promise 来实现微任务)
    Promise.resolve().then(run);
  }

  // 定义 reject 函数
  const reject = (error) => {
    const run = () => {
      // 如果状态已经改变了,不会再执行了
      if (_status !== PENDING) {
        return;
      }
      
      // 状态变更为 rejected
      _status = REJECTED;

      // 记录执行结果
      _result = error;

      // 依次调用 onRejected 回调
      _callbacks.forEach(cb => handleCB(cb));
    }

    // 异步以微任务形式执行(借助原生 Promise 来实现微任务)
    Promise.resolve().then(run);
  }

  try {
    // 参数函数立即执行,并将 resolve 和 reject 函数作为参数传入
    handle.call(null, resolve, reject);
  } catch (e) {
    reject(e);
  }
}

Promise 静态方法

Promise 也提供了一些静态方法,接下来我们也来实现下。

Promise.all()

Promise.all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);
  • 只有 p1、p2、p3 状态都变成 fulfilled,p 的状态才会变成 fulfilled。此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数。
  • 只要 p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数。

下面看个具体的例子:

const p1 = new Promise(resolve => resolve('promise 1 resolve'));
const p2 = new Promise(resolve => resolve('promise 2 resolve'));
const p3 = new Promise(resolve => resolve('promise 3 resolve'));
const p4 = new Promise((resolve, reject) => reject('promise 4 reject'));

Promise.all([p1, p2, p3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))

Promise.all([p1, p2, p4])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))

接下来我们实现下 MyPromise.all。思路也很简单,为每一个 MyPromise 添加 onFulfilledonRejected 回调,如果是 onRejected,那可以直接 reject 了。如果 onFulfilled,需要记录 onFulfilled 数量,全部 onFulfilled 才能 resolve。还有一点就是异步结果是数组而且位置与入参的 promises 一致,所以我们也需要记录各个 MyPromise 对象的位置。具体实现代码如下:

MyPromise.all = function(promises) {
  return new MyPromise((resolve, reject) => {
    const result = []; // 记录结果
    let num = 0; // fulfilled 个数
    const total = promises.length; // 总数

    // 返回结果和位置一致,所以需要记录下位置信息 i
    const _resolve = (res, i) => {
      num++; // fulfilled 次数加一
      result[i] = res;
      // 全部 fulfilled
      if (num === total) {
        resolve(result);
      }
    };

    // 有 reject,直接就reject
    const _reject = (error) => {
      reject(error);
    };

    promises.forEach((p, i) => {
      p.then(res => _resolve(res, i), error => _reject(error));
    })
  })
}

接下来测试下:

const p1 = new MyPromise(resolve => resolve('MyPromise 1 resolve'));
const p2 = new MyPromise(resolve => resolve('MyPromise 2 resolve'));
const p3 = new MyPromise(resolve => resolve('MyPromise 3 resolve'));
const p4 = new MyPromise((resolve, reject) => reject('MyPromise 4 reject'));

MyPromise.all([p1, p2, p3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))

MyPromise.all([p1, p2, p4])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))

运行结果和 Promise 一致,搞定。

Promise.race()

Promise.race() 方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要 p1、p2、p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

具体实现很简单,由于 resolvereject 只会执行一次,一旦有状态改变,后续就不会再执行了,借助这个可以,可以实现如下:

MyPromise.race = function(promises) {
  return new MyPromise((resolve, reject) => {
    promises.forEach((p, i) => {
      p.then(res => resolve(res), error => reject(error));
    })
  })
}

测试对比下:

// 测试用例 Promise.race
const p1 = new Promise(resolve => setTimeout(() => {
  resolve('promise 1 resolve')
}, 200));

const p2 = new Promise(resolve => setTimeout(() => {
  resolve('promise 2 resolve')
}, 300));

const p3 = new Promise((resolve, reject) => setTimeout(() => {
  reject('promise 3 reject')
}, 100));

Promise.race([p1, p2, p3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))


const mp1 = new MyPromise(resolve => setTimeout(() => {
  resolve('MyPromise 1 resolve')
}, 200));

const mp2 = new MyPromise(resolve => setTimeout(() => {
  resolve('MyPromise 2 resolve')
}, 300));

const mp3 = new MyPromise((resolve, reject) => setTimeout(() => {
  reject('MyPromise 3 reject')
}, 100));

MyPromise.race([mp1, mp2, mp3])
.then(r => console.log('fulfilled', r))
.catch(r => console.log('reject', r))

由于 p3 和 mp3 率先实现状态转变,所以最终的结果就是 p3 和 mp3 的执行结果。

Promise.allSettled()

ES2020 引入了 Promise.allSettled() 方法,用来确定一组异步操作是否都结束了(不管成功或失败)。

Promise.allSettled() 方法接受一个数组作为参数,数组的每个成员都是一个 Promise 对象,并返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是 fulfilled 还是rejected ),返回的 Promise 对象才会发生状态变更。

它的返回值格式是这样的:

{ status: 'fulfilled', value: 42 } // resolve
{ status: 'rejected', reason: -1 } // reject

具体实现如下:

MyPromise.allSettled = function(promises) {
  return new MyPromise((resolve, reject) => {
    const result = []; // 记录结果
    let num = 0; // 状态已完成个数
    const total = promises.length; // 总数

    // 返回结果和位置一致,所以需要记录下位置信息 i
    const _handle = (res, i) => {
      num++; // 已完成次数加一
      result[i] = res;
      // 是否已经全部完成
      if (num === total) {
        resolve(result);
      }
    };

    promises.forEach((p, i) => {
      p.then(
        res => _handle({ status: 'fulfilled', value: res }, i),
        error => _handle({ status: 'rejected', reason: error }, i)
      );
    })
  })
}

测试对比下

// 测试用例 Promise.allSettled
const p1 = new Promise(resolve => resolve('promise 1 resolve'));
const p2 = new Promise(resolve => resolve('promise 2 resolve'));
const p3 = new Promise((resolve, reject) => reject('promise 3 reject'));
Promise.allSettled([p1, p2, p3]).then(r => console.log('promise', r))

const mp1 = new MyPromise(resolve => resolve('MyPromise 1 resolve'));
const mp2 = new MyPromise(resolve => resolve('MyPromise 2 resolve'));
const mp3 = new MyPromise((resolve, reject) => reject('MyPromise 3 reject'));
MyPromise.allSettled([mp1, mp2, mp3]).then(r => console.log('MyPromise', r))

测试结果与 Promise 一致。

Promise.any()

ES2021 引入了 Promise.any() 方法。该方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

只要参数实例有一个变成 fulfilled 状态,包装实例就会变成 fulfilled 状态;如果所有参数实例都变成rejected 状态,包装实例就会变成 rejected 状态。

我们简单实现下:

MyPromise.any = function(promises) {
  return new MyPromise((resolve, reject) => {
    let num = 0; // rejected 个数
    const total = promises.length; // 总数

    // 有实例 resolve,直接 resolve
    const _resolve = res => {
      resolve(res);
    };

    // 全部 reject,抛出错误
    const _reject = () => {
      num++; // rejected 次数加一
      // 全部 rejected
      if (num === total) {
        reject('AggregateError: All promises were rejected')
      }
    };

    promises.forEach((p) => {
      p.then(res => _resolve(res), error => _reject());
    })
  })
}

测试下

// 测试用例 Promise.any
const mp1 = new MyPromise(resolve => resolve('MyPromise 1 resolve'));
const mp2 = new MyPromise((resolve, reject) => reject('MyPromise 2 reject'));
const mp3 = new MyPromise((resolve, reject) => reject('MyPromise 3 reject'));

MyPromise.any([mp1, mp2])
.then(r => console.log('MyPromise then', r))
.catch(error => console.log('MyPromise error', error))

MyPromise.any([mp2, mp3])
.then(r => console.log('MyPromise then', r))
.catch(error => console.log('MyPromise error', error))

// MyPromise then MyPromise 1 resolve
// MyPromise error AggregateError: All promises were rejected

参考资料

Promise 对象 - ECMAScript 6入门