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

前言

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

 

Promise 的做用

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

 

课前知识

观察者模式

什么是观察者模式:git

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

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

事件循环机制

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

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

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

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);
复制代码

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

Promises/A+ 规范

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

 

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 包装成一个微任务?

实不相瞒,想要个赞!

 

参考文档

 

示例代码

示例代码能够看这里:

相关文章
相关标签/搜索