30分钟,带你实现一个符合规范的 Promise(巨详细)

前言

关于 Promise 原理解析的优秀文章,在掘金上已经有很是多了。可是笔者老是处在 看了就会,一写就废 的状态,这是笔者写这篇文章的目的,为了理一下 Promise 的编写思路,从零开始手写一波代码,同时也方便本身往后回顾。javascript

 

Promise 的做用

PromiseJavaScript 异步编程的一种流行解决方案,它的出现是为了解决 回调地狱 的问题,让使用者能够经过链式的写法去编写写异步代码,具体的用法笔者就不介绍了,你们能够参考阮一峰老师的 ES6 Promise教程java

 

课前知识

观察者模式

什么是观察者模式:git

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知全部观察者对象,使它们可以自动更新。

Promise 是基于 观察者的设计模式 实现的,then 函数要执行的函数会被塞入观察者数组中,当 Promise 状态变化的时候,就去执行观察组数组中的全部函数。es6

事件循环机制

实现 Promise 涉及到了 JavaScript 中的事件循环机制 EventLoop、以及宏任务和微任务的概念。github

事件循环机制的流程图以下:面试

你们能够看一下这段代码:typescript

console.log(1);

setTimeout(() => {
  console.log(2);
},0);

let a = new Promise((resolve) => {
  console.log(3);
  resolve();
}).then(() => {
  console.log(4);
}).then(() => {
  console.log(5);
});

console.log(6);

若是不能一会儿说出输出结果,建议你们能够先查阅一下 事件循环 的相关资料,在掘金中有不少优秀的文章。shell

Promises/A+ 规范

Promises/A+ 是一个社区规范,若是你想写出一个规范的 Promise,咱们就须要遵循这个标准。以后咱们也会根据规范来完善咱们本身编写的 Promisenpm

 

Promise 核心知识点

在动手写 Promise 以前,咱们先过一下几个重要的知识点。编程

executor

// 建立 Promise 对象 x1
// 并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
  // 业务逻辑处理成功结果
  const value = ...;
  resolve(value);
  // 失败结果
  // const reason = ...;
  // reject(reason);
}

let x1 = new Promise(executor);

首先 Promise 是一个类,它接收一个执行函数 executor,它接收两个参数:resolvereject,这两个参数是 Promise 内部定义的两个函数,用来改变状态并执行对应回调函数。

由于 Promise 自己是不知道执行结果失败或者成功,它只是给异步操做提供了一个容器,实际上的控制权在使用者的手上,使用者能够调用上面两个参数告诉 Promise 结果是否成功,同时将业务逻辑处理结果( value/reason)做为参数传给 resolvereject 两个函数,执行回调。

三个状态

Promise 有三个状态:

  • pending:等待中
  • resolved:已成功
  • rejected:已失败

Promise 的状态改变只有两种可能:从 pending 变为 resolved 或者从 pending 变为 rejected,以下图(引自 Promise 迷你书):

引自 Promise 迷你书

并且须要注意的是一旦状态改变,状态不会再变了,接下来就一直是这个结果。也就是说当咱们在 executor 函数中调用了 resolve 以后,以后调用 reject 就没有效果了,反之亦然。

// 并在 executor 函数中执行业务逻辑
function executor(resolve, reject){
  resolve(100);
  // 以后调用 resolve,reject 都是无效的,
  // 由于状态已经变为 resolved,不会再改变了
  reject(100);
}

let x1 = new Promise(executor);

then

每个 promise 都一个 then 方法,这个是当 promise 返回结果以后,须要执行的回调函数,他有两个可选参数:

  • onFulfilled:成功的回调;
  • onRejected:失败的回调;

以下图(引自 Promise 迷你书):

引自 Promise 迷你书

// ...
let x1 = new Promise(executor);

// x1 延迟绑定回调函数 onResolve
function onResolved(value){
  console.log(value);
}

// x1 延迟绑定回调函数 onRejected
function onRejected(reason){
  console.log(reason);
}

x1.then(onResolved, onRejected);

 

手写 Promise 大体流程

在这里咱们简单过一下手写一个 Promise 的大体流程:

executor 与三个状态

  • new Promise 时,须要传递一个 executor 执行器函数,在构造函数中,执行器函数马上执行
  • executor 执行函数接受两个参数,分别是 resolvereject
  • Promise 只能从 pendingrejected, 或者从 pendingfulfilled
  • Promise 的状态一旦确认,状态就凝固了,不在改变

then 方法

  • 全部的 Promise 都有 then 方法,then 接收两个参数,分别是 Promise 成功的回调 onFulfilled,和失败的回调 onRejected
  • 若是调用 then 时,Promise 已经成功,则执行 onFulfilled,并将 Promise 的值做为参数传递进去;若是 Promise 已经失败,那么执行 onRejected,并将 Promise 失败的缘由做为参数传递进去;若是 Promise 的状态是 pending,须要将 onFulfilledonRejected 函数存放起来,等待状态肯定后,再依次将对应的函数执行(观察者模式)
  • then 的参数 onFulfilledonRejected 能够不传,Promise 能够进行值穿透

链式调用并处理 then 返回值

  • Promise 能够 then 屡次,Promisethen 方法返回一个新的 Promise
  • 若是 then 返回的是一个正常值,那么就会把这个结果(value)做为参数,传递给下一个 then 的成功的回调(onFulfilled
  • 若是 then 中抛出了异常,那么就会把这个异常(reason)做为参数,传递给下一个 then 的失败的回调(onRejected)
  • 若是 then 返回的是一个 promise 或者其余 thenable 对象,那么须要等这个 promise 执行完撑,promise 若是成功,就走下一个 then 的成功回调;若是失败,就走下一个 then 的失败回调。

上面是大体的实现流程,若是迷迷糊糊不要紧,只要大体有一个印象便可,后续咱们会一一讲到。

那接下来咱们就开始实现一个最简单的例子开始讲解。

 

初版(从一个简单例子开始)

咱们先写一个简单版,这版暂不支持状态、链式调用,而且只支持调用一个 then 方法。

来个 🌰

let p1 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
      resolved('成功了');
    }, 1000);
})

p1.then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
})

例子很简单,就是 1s 以后返回 成功了,并在 then 中输出。

实现

咱们定义一个 MyPromise 类,接着咱们在其中编写代码,具体代码以下:

class MyPromise {
  // ts 接口定义 ...
  constructor (executor: executor) {
    // 用于保存 resolve 的值
    this.value = null;
    // 用于保存 reject 的值
    this.reason = null;
    // 用于保存 then 的成功回调
    this.onFulfilled = null;
    // 用于保存 then 的失败回调
    this.onRejected = null;

    // executor 的 resolve 参数
    // 用于改变状态 并执行 then 中的成功回调
    let resolve = value => {
      this.value = value;
      this.onFulfilled && this.onFulfilled(this.value);
    }

    // executor 的 reject 参数
    // 用于改变状态 并执行 then 中的失败回调
    let reject = reason => {
      this.reason = reason;
      this.onRejected && this.onRejected(this.reason);
    }

    // 执行 executor 函数
    // 将咱们上面定义的两个函数做为参数 传入
    // 有可能在 执行 executor 函数的时候会出错,因此须要 try catch 一下 
    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }

  // 定义 then 函数
  // 而且将 then 中的参数复制给 this.onFulfilled 和 this.onRejected
  private then(onFulfilled, onRejected) {
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
  }
}

好了,咱们的初版就完成了,是否是很简单。

不过这里须要注意的是, resolve 函数的执行时机须要在 then 方法将回调函数注册了以后,在 resolve 以后在去往赋值回调函数,其实已经完了,没有任何意义。

上面的例子没有问题,是由于 resolve(成功了) 是包在 setTimeout 中的,他会在下一个宏任务执行,这时回调函数已经注册了。

你们能够试试把 resolve(成功了)setTimeout 中拿出来,这个时候就会出现问题了。

存在问题

这一版实现很简单,还存在几个问题:

  • 未引入状态的概念

未引入状态的概念,如今状态能够随意变,不符合 Promise 状态只能从等待态变化的规则。

  • 不支持链式调用

正常状况下咱们能够对 Promise 进行链式调用:

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

p1.then(onResolved1, onRejected1).then(onResolved2, onRejected2)
  • 只支持一个回调函数,若是存在多个回调函数的话,后面的会覆盖前面的

在这个例子中,onResolved2 会覆盖 onResolved1onRejected2 会覆盖 onRejected1

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

// 注册多个回调函数
p1.then(onResolved1, onRejected1);
p1.then(onResolved2, onRejected2);

接下来咱们更进一步,把这些问题给解决掉。

 

第二版(实现链式调用)

这一版咱们把状态的概念引入,同时实现链式调用的功能。

加上状态

上面咱们说到 Promise 有三个状态:pendingresovledrejected,只能从 pending 转为 resovled 或者 rejected,并且当状态改变以后,状态就不能再改变了。

  • 咱们定义一个属性 status:用于记录当前 Promise 的状态
  • 为了防止写错,咱们把状态定义成常量 PENDINGRESOLVEDREJECTED
  • 同时咱们将保存 then 的成功回调定义为一个数组:this.resolvedQueuesthis.rejectedQueues,咱们能够把 then 中的回调函数都塞入对应的数组中,这样就能解决咱们上面提到的第三个问题。
class MyPromise {
  private static PENDING = 'pending';
  private static RESOLVED = 'resolved';
  private static REJECTED = 'rejected';

  constructor (executor: executor) {
    this.status = MyPromise.PENDING;
    // ...

    // 用于保存 then 的成功回调数组
    this.resolvedQueues = [];
    // 用于保存 then 的失败回调数组
    this.rejectedQueues = [];

    let resolve = value => {
      // 当状态是 pending 是,将 promise 的状态改成成功态
      // 同时遍历执行 成功回调数组中的函数,将 value 传入
      if (this.status == MyPromise.PENDING) {
        this.value = value;
        this.status = MyPromise.RESOLVED;
        this.resolvedQueues.forEach(cb => cb(this.value))
      }
    }

    let reject = reason => {
      // 当状态是 pending 是,将 promise 的状态改成失败态
      // 同时遍历执行 失败回调数组中的函数,将 reason 传入
      if (this.status == MyPromise.PENDING) {
        this.reason = reason;
        this.status = MyPromise.REJECTED;
        this.rejectedQueues.forEach(cb => cb(this.reason))
      }
    }

    try {
      executor(resolve, reject);
    } catch(err) {
      reject(err);
    }
  }
}

完善 then 函数

接着咱们来完善 then 中的方法,以前咱们是直接将 then 的两个参数 onFulfilledonRejected,直接赋值给了 Promise 的用于保存成功、失败函数回调的实例属性。

如今咱们须要将这两个属性塞入到两个数组中去:resolvedQueuesrejectedQueues

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // 首先判断两个参数是否为函数类型,由于这两个参数是可选参数
    // 当参数不是函数类型时,须要建立一个函数赋值给对应的参数
    // 这也就实现了 透传
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    // 当状态是等待态的时候,须要将两个参数塞入到对应的回调数组中
    // 当状态改变以后,在执行回调函数中的函数
    if (this.status === MyPromise.PENDING) {
      this.resolvedQueues.push(onFulfilled)
      this.rejectedQueues.push(onRejected)
    }

    // 状态是成功态,直接就调用 onFulfilled 函数
    if (this.status === MyPromise.RESOLVED) {
      onFulfilled(this.value)
    }

    // 状态是成功态,直接就调用 onRejected 函数
    if (this.status === MyPromise.REJECTED) {
      onRejected(this.reason)
    }
  }
}

then 函数的一些说明

  • 什么状况下 this.status 会是 pending 状态,什么状况下会是 resolved 状态

这个其实也和事件循环机制有关,以下代码:

// this.status 为 pending 状态
new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve(1)
  }, 0)
}).then(value => {
  console.log(value)
})

// this.status 为 resolved 状态
new MyPromise((resolve, reject) => {
  resolve(1)
}).then(value => {
  console.log(value)
})
  • 什么是 透传

以下面代码,当 then 中没有传任何参数的时候,Promise 会使用内部默认的定义的方法,将结果传递给下一个 then

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolved('成功了');
  }, 1000);
})

p1.then().then((res) => {
  console.log(res);
})

由于咱们如今还没支持链式调用,这段代码运行会出问题。

支持链式调用

支持链式调用,其实很简单,咱们只须要给 then 函数最后返回 this 就行,这样就支持了链式调用:

class MyPromise {
  // ...
  private then(onFulfilled, onRejected) {
    // ...
    return this;
  }
}

每次调用 then 以后,咱们都返回当前的这个 Promise 对象,由于 Promise 对象上是存在 then 方法的,这个时候咱们就简单的实现了 Promise 的简单调用。

这个时候运行上面 透传 的测试代码了。

可是上面的代码仍是存在相应的问题的,看下面代码:

const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');  
});

p1.then((res) => {
  console.log(res);
  return 'then1';
})
.then((res) => {
  console.log(res);
  return 'then2';
})
.then((res) => {
  console.log(res);
  return 'then3';
})

// 预测输出:resolved -> then1 -> then2
// 实际输出:resolved -> resolved -> resolved

输出与咱们的预期有误差,由于咱们 then 中返回的 this 表明了 p1,在 new MyPromise 以后,其实状态已经从 pending 态变为了 resolved 态,以后不会再变了,因此在 MyPromise 中的 this.value 值就一直是 resolved

这个时候咱们就得看看关于 then 返回值的相关知识点了。

then 返回值

实际上 then 都会返回了一个新的 Promise 对象。

先看下面这段代码:

// 新建立一个 promise
const aPromise = new Promise(function (resolve) {
  resolve(100);
});

// then 返回的 promise
var thenPromise = aPromise.then(function (value) {
  console.log(value);
});

console.log(aPromise !== thenPromise); // => true

从上面的代码中咱们能够得出 then 方法返回的 Promise 已经再也不是最初的 Promise 了,以下图(引自 Promise 迷你书):

引自 Promise 迷你书

promise 的链式调用跟 jQuery 的链式调用是有区别的, jQuery 链式调用返回的对象仍是最初那个 jQuery 对象; Promise 更相似于数组中一些方法,如 slice,每次进行操做以后,都会返回一个新的值。

改造代码

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

    // then 方法返回一个新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功状态,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 将 onFulfilled 函数的返回值,resolve 出去
        let x = onFulfilled(this.value);
        resolve(x);
      }

      // 失败状态,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 将 onRejected 函数的返回值,reject 出去
        let x = onRejected(this.reason)
        reject && reject(x);
      }

      // 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push((value) => {
          let x = onFulfilled(value);
          resolve(x);
        })
        this.rejectedQueues.push((reason) => {
          let x = onRejected(reason);
          reject && reject(x);
        })
      }
    });
    return promise2;
  }
}

// 输出结果 resolved -> then1 -> then2

存在问题

到这里咱们就完成了简单的链式调用,可是只能支持同步的链式调用,若是咱们须要在 then 方法中再去进行其余异步操做的话,上面的代码就 GG 了。

以下代码:

const p1 = new MyPromise((resolved, rejected) => {
  resolved('我 resolved 了');  
});

p1.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then1');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  console.log(res);
  return 'then3';
})

上面的代码会直接将 Promise 对象直接看成参数传给下一个 then 函数,而咱们实际上是想要将这个 Promise 的处理结果传递下去。

 

第三版(异步链式调用)

这一版咱们来实现 promise 的异步链式调用。

思路

先看一下 thenonFulfilledonRejected 返回的值:

// 成功的函数返回
let x = onFulfilled(this.value);

// 失败的函数返回
let x = onRejected(this.reason);

从上面的的问题中能够看出,x 能够是一个 普通值,也能够是一个 Promise 对象,普通值的传递咱们在 第二版 已经解决了,如今须要解决的是当 x 返回一个 Promise 对象的时候该怎么处理。

其实也很简单,当 x 是一个 Promise 对象的时候,咱们须要进行等待,直到返回的 Promise 状态变化的时候,再去执行以后的 then 函数,代码以下:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason}

    // then 方法返回一个新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功状态,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 将 onFulfilled 函数的返回值,resolve 出去
        let x = onFulfilled(this.value);
        resolvePromise(promise2, x, resolve, reject);
      }

      // 失败状态,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 将 onRejected 函数的返回值,reject 出去
        let x = onRejected(this.reason)
        resolvePromise(promise2, x, resolve, reject);
      }

      // 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          let x = onFulfilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
        this.rejectedQueues.push(() => {
          let x = onRejected(this.reason);
          resolvePromise(promise2, x, resolve, reject);
        })
      }
    });
    return promise2;
  }
}

咱们新写一个函数 resolvePromise,这个函数是用来处理异步链式调用的核心方法,他会去判断 x 返回值是否是 Promise 对象,若是是的话,就直到 Promise 返回成功以后在再改变状态,若是是普通值的话,就直接将这个值 resovle 出去:

const resolvePromise = (promise2, x, resolve, reject) => {
  if (x instanceof MyPromise) {
    const then = x.then;
    if (x.status == MyPromise.PENDING) {
      then.call(x, y => {
        resolvePromise(promise2, y, resolve, reject);
      }, err => {
        reject(err);
      })
    } else {
      x.then(resolve, reject);
    }
  } else {
    resolve(x);
  }
}

代码说明

resolvePromise

resolvePromise 接受四个参数:

  • promise2then 中返回的 promise
  • xthen 的两个参数 onFulfilled 或者 onRejected 的返回值,类型不肯定,有多是普通值,有多是 thenable 对象;
  • resolverejectpromise2 的。

then 返回值类型

xPromise 的时,而且他的状态是 Pending 状态,若是 x 执行成功,那么就去递归调用 resolvePromise 这个函数,将 x 执行结果做为 resolvePromise 第二个参数传入;

若是执行失败,则直接调用 promise2reject 方法。

 

到这里咱们基本上一个完整的 promise,接下来咱们须要根据 Promises/A+ 来规范一下咱们的 Promise

 

规范 Promise

前几版的代码笔者基本上是按照规范来的,这里主要讲几个没有符合规范的点。

规范 then(规范 2.2)

thenonFulfilledonRejected 须要异步执行,即放到异步任务中去执行(规范 2.2.4)

实现

咱们须要将 then 中的函数经过 setTimeout 包裹起来,放到一个宏任务中去,这里涉及了 jsEventLoop,你们能够去看看相应的文章,以下:

class MyPromise {
  // ...

  private then(onFulfilled, onRejected) {
    // ...
    // then 方法返回一个新的 promise
    const promise2 = new MyPromise((resolve, reject) => {
      // 成功状态,直接 resolve
      if (this.status === MyPromise.RESOLVED) {
        // 将 onFulfilled 函数的返回值,resolve 出去
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      // 失败状态,直接 reject
      if (this.status === MyPromise.REJECTED) {
        // 将 onRejected 函数的返回值,reject 出去
        setTimeout(() => {
          try {
            let x = onRejected(this.reason)
            resolvePromise(promise2, x, resolve, reject);
          } catch(err) {
            reject(err);
          }
        })
      }

      // 等待状态,将 onFulfilled,onRejected 塞入数组中,等待回调执行
      if (this.status === MyPromise.PENDING) {
        this.resolvedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
        this.rejectedQueues.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason)
              resolvePromise(promise2, x, resolve, reject);
            } catch(err) {
              reject(err);
            }
          })
        })
      }
    });
    return promise2;
  }
}

使用微任务包裹

但这样仍是有一个问题,咱们知道其实 Promise.then 是属于微任务的,如今当使用 setTimeout 包裹以后,就至关于会变成一个宏任务,能够看下面这一个例子:

var p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

setTimeout(() => {
  console.log('---setTimeout---');
}, 0);

p1.then(res => {
  console.log('---then---');
})

// 正常 Promise:then -> setTimeout
// 咱们的 Promise:setTimeout -> then

输出顺序不同,缘由是由于如今的 Promise 是经过 setTimeout 宏任务包裹的。

咱们能够改进一下,使用微任务来包裹 onFulfilledonRejected,经常使用的微任务有 process.nextTickMutationObserverpostMessage 等,咱们这个使用 postMessage 改写一下:

// ...
if (this.status === MyPromise.RESOLVED) {
  // 将 onFulfilled 函数的返回值,resolve 出去
  // 注册一个 message 事件
  window.addEventListener('message', event => {
    const { type, data } =  event.data;

    if (type === '__promise') {
      try {
        let x = onFulfilled(that.value);
        resolvePromise(promise2, x, resolve, reject);
      } catch(err) {
        reject(err);
      }
    }
  });
  // 立马执行
  window.postMessage({
    type: '__promise',
  }, "http://localhost:3001");
}

// ...

实现方法很简单,咱们监听windowmessage 事件,并在以后立马触发一个 postMessage 事件,这个时候其实 then 中的回调函数已经在微任务队列中了,咱们从新运行一下例子,能够看到输出的顺序变为了 then -> setTimeout

固然 Promise 内部实现确定没有这么简单,笔者在这里只是提供一种思路,你们有兴趣能够去研究一波。

规范 resolvePromise 函数(规范 2.3)

重复引用

重复引用,当 xpromise2 是同样的,那就须要报一个错误,重复应用。(规范 2.3.1)



由于本身等待本身完成是永远都不会有结果的。
const p1 = new MyPromise((resolved, rejected) => {
  resolved('我 resolved 了');  
});

const p2 = p1.then((res) => {
  return p2;
});

x 的类型

大体分为一下这么几条:

  • 2.3.2:当 x 是一个 Promise,那么就等待 x 改变状态以后,才算完成或者失败(这个也属于 2.3.3,由于 Promise 其实也是一个 thenable 对象)
  • 2.3.3:当 x 是一个对象 或者 函数的时候,即 thenable 对象,那就那 x.then 做为 then
  • 2.3.4:当 x 不是一个对象,或者函数的时候,直接将 x 做为参数 resolve 返回。

咱们主要看一下 2.3.3 就行,由于 Prmise 也属于 thenable 对象,那什么是 thenable 对象呢?

简单来讲就是具备 then方法的对象/函数,全部的 Promise 对象都是 thenable 对象,但并不是全部的 thenable 对象并不是是 Promise 对象。以下:

let thenable = {
 then: function(resolve, reject) {
   resolve(100);
 }
}

根据 x 的类型进行处理:

  • 若是 x 不是 thenable 对象,直接调用 Promise2resolve,将 x 做为成功的结果;
  • xthenable 对象,会调用 xthen 方法,成功后再去调用 resolvePromise 函数,并将执行结果 y 做为新的 x 传入 resolvePromise,直到这个 x 值再也不是一个 thenable 对象为止;若是失败则直接调用 promise2reject
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
  if (typeof then === 'function') {
    then.call(x, (y) => {
      resolvePromise(promise2, y, resolve, reject);
    }, (err) => {
      reject(err);
    })
  }
} else {
  resolve(x);
}

只调用一次

规范( Promise/A+ 2.3.3.3.3)规定若是同时调用 resolvePromiserejectPromise,或者对同一参数进行了屡次调用,则第一个调用优先,而全部其余调用均被忽略,确保只执行一次改变状态。

咱们在外面定义了一个 called 占位符,为了得到 then 函数有没有执行过相应的改变状态的函数,执行过了以后,就再也不去执行了,主要就是为了知足规范。

x 为 Promise 对象

若是 xPromise 对象的话,其实当执行了resolve 函数 以后,就不会再执行 reject 函数了,是直接在当前这个 Promise 对象就结束掉了。

x 为 thenable 对象

x 是普通的 thenable 函数的时候,他就有可能同时执行 resolvereject 函数,便可以同时执行 promise2resolve 函数 和 reject 函数,可是其实 promise2 在状态改变了以后,也不会再改变相应的值了。其实也没有什么问题,以下代码:

// thenable 对像
{
 then: function(resolve, reject) {
   setTimeout(() => {
     resolve('我是thenable对像的 resolve');
     reject('我是thenable对像的 reject')
    })
 }
}

完整的 resolvePromise

完整的 resolvePromise 函数以下:

const resolvePromise = (promise2, x, resolve, reject) => {
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}

到这里就大功告成了,开不开心,兴不兴奋!

最后咱们能够经过测试脚本跑一下咱们的 MyPromise 是否符合规范。

测试

有专门的测试脚本(promises-aplus-tests)能够帮助咱们测试所编写的代码是否符合 Promise/A+ 的规范。

可是貌似只能测试 js 文件,因此笔者就将 ts 文件转化为了 js 文件,进行测试

在代码里面加上:

// 执行测试用例须要用到的代码
MyPromise.deferred = function() {
  let defer = {};
  defer.promise = new MyPromise((resolve, reject) => {
      defer.resolve = resolve;
      defer.reject = reject;
  });
  return defer;
}

须要提早安装一下测试插件:

# 安装测试脚本
npm i -g promises-aplus-tests

# 开始测试
promises-aplus-tests MyPromise.js

结果以下:

完美经过,接下去咱们就能够看看 Promise 更多方法的实现了。

 

更多方法

实现上面的 Promise 以后,其实编写其实例和静态方法,相对来讲就简单了不少。

实例方法

Promise.prototype.catch

实现

其实这个方法就是 then 方法的语法糖,只须要给 then 传递 onRejected 参数就 ok 了。

private catch(onRejected) {
  return this.then(null, onRejected);
}
例子:
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      rejected('错误了');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('----error', error);
})

// 1s 以后输出:----error 错误了

Promise.prototype.finally

实现

finally() 方法用于指定无论 Promise 对象最后状态如何,都会执行的操做。

private finally (fn) {
  return this.then(fn, fn);
}
例子
const p1 = new MyPromise((resolved, rejected) => {
  resolved('resolved');
})

p1.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      rejected('错误了');
    }, 1000)
  });
})
.then((res) => {
  return new MyPromise((resolved, rejected) => {
    setTimeout(() => {
      resolved('then2');
    }, 1000)
  });
})
.then((res) => {
  return 'then3';
}).catch(error => {
  console.log('---error', error);
  return `catch-${error}`
}).finally(res => {
  console.log('---finally---', res);
})

// 输出结果:---error 错误了" -> ""---finally--- catch-错误了

 

静态方法

Promise.resolve

实现

有时须要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个做用。

static resolve = (val) => {
  return new MyPromise((resolve,reject) => {
    resolve(val);
  });
}
例子
MyPromise.resolve({name: 'darrell', sex: 'boy' }).then((res) => {
  console.log(res);
}).catch((error) => {
  console.log(error);
});

// 输出结果:{name: "darrell", sex: "boy"}

Promise.reject

实现

Promise.reject(reason) 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected

static reject = (val) => {
  return new MyPromise((resolve,reject) => {
    reject(val)
  });
}
例子
MyPromise.reject("出错了").then((res) => {
  console.log(res);
}).catch((error) => {
  console.log(error);
});

// 输出结果:出错了

Promise.all

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

const p = Promise.all([p1, p2, p3]);
  • 只有 p1p2p3 的状态都变成 fulfilledp 的状态才会变成 fulfilled
  • 只要 p1p2p3 之中有一个被 rejectedp 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给p的回调函数。
实现
static all = (promises: MyPromise[]) => {
  return new MyPromise((resolve, reject) => {
    let result: MyPromise[] = [];
    let count = 0;

    for (let i = 0; i < promises.length; i++) {
      promises[i].then(data => {
        result[i] = data;
        if (++count == promises.length) {
          resolve(result);
        }
      }, error => {
        reject(error);
      });
    }
  });
}
例子
let Promise1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise1');
  }, 2000);
});

let Promise2 = new MyPromise((resolve, reject) => {
  resolve('Promise2');
});

let Promise3 = new MyPromise((resolve, reject) => {
  resolve('Promise3');
})

let Promise4 = new MyPromise((resolve, reject) => {
  reject('Promise4');
})

let p = MyPromise.all([Promise1, Promise2, Promise3, Promise4]);

p.then((res) => {
  // 三个都成功则成功  
  console.log('---成功了', res);
}).catch((error) => {
  // 只要有失败,则失败 
  console.log('---失败了', err);
});

// 直接输出:---失败了 Promise4

Promise.race

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

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

只要 p1p2p3 之中有一个实例率先改变状态,p 的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 p 的回调函数。

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

例子和 all 同样,调用以下:

// ...

let p = MyPromise.race([Promise1, Promise2, Promise3, Promise4])

p.then((res) => { 
  console.log('---成功了', res);
}).catch((error) => {
  console.log('---失败了', err);
});

// 直接输出:---成功了 Promise2

Promise.allSettled

此方法接受一组 Promise 实例做为参数,包装成一个新的 Promise 实例。

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

只有等到全部这些参数实例都返回结果,不论是 fulfilled 仍是 rejected,并且该方法的状态只可能变成 fulfilled

此方法与 Promise.all 的区别是 all 没法肯定全部请求都结束,由于在 all 中,若是有一个被 Promiserejectedp 的状态就立马变成 rejected,有可能有些异步请求还没走完。
实现
static allSettled = (promises: MyPromise[]) => {
  return new MyPromise((resolve) => {
    let result: MyPromise[] = [];
    let count = 0;
    for (let i = 0; i < promises.length; i++) {
      promises[i].finally(res => {
        result[i] = res;
        if (++count == promises.length) {
          resolve(result);
        }
      })
    }
  });
}
例子

例子和 all 同样,调用以下:

let p = MyPromise.allSettled([Promise1, Promise2, Promise3, Promise4])

p.then((res) => {
  // 三个都成功则成功  
  console.log('---成功了', res);
}, err => {
  // 只要有失败,则失败 
  console.log('---失败了', err);
})

// 2s 后输出:---成功了 (4) ["Promise1", "Promise2", "Promise3", "Promise4"]

 

总结

这篇文章笔者带你们一步一步的实现了符合 Promise/A+ 规范的的 Promise,看完以后相信你们基本上也可以本身独立写出一个 Promise 来了。

最后经过几个问题,你们能够看看本身掌握的如何:

  • Promise 中是如何实现回调函数返回值穿透的?
  • Promise 出错后,是怎么经过 冒泡 传递给最后那个捕获异常的函数?
  • Promise 如何支持链式调用?
  • 怎么将 Promise.then 包装成一个微任务?

实不相瞒,想要个赞!

 

参考文档

 

示例代码

示例代码能够看这里:

相关文章
相关标签/搜索