关于 Promise
原理解析的优秀文章,在掘金上已经有很是多了。可是笔者老是处在 看了就会,一写就废 的状态,这是笔者写这篇文章的目的,为了理一下 Promise
的编写思路,从零开始手写一波代码,同时也方便本身往后回顾。javascript
Promise
是 JavaScript
异步编程的一种流行解决方案,它的出现是为了解决 回调地狱 的问题,让使用者能够经过链式的写法去编写写异步代码,具体的用法笔者就不介绍了,你们能够参考阮一峰老师的 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+ 是一个社区规范,若是你想写出一个规范的 Promise
,咱们就须要遵循这个标准。以后咱们也会根据规范来完善咱们本身编写的 Promise
。npm
在动手写 Promise
以前,咱们先过一下几个重要的知识点。编程
// 建立 Promise 对象 x1 // 并在 executor 函数中执行业务逻辑 function executor(resolve, reject){ // 业务逻辑处理成功结果 const value = ...; resolve(value); // 失败结果 // const reason = ...; // reject(reason); } let x1 = new Promise(executor);
首先 Promise
是一个类,它接收一个执行函数 executor
,它接收两个参数:resolve
和 reject
,这两个参数是 Promise
内部定义的两个函数,用来改变状态并执行对应回调函数。
由于Promise
自己是不知道执行结果失败或者成功,它只是给异步操做提供了一个容器,实际上的控制权在使用者的手上,使用者能够调用上面两个参数告诉Promise
结果是否成功,同时将业务逻辑处理结果(value/reason
)做为参数传给resolve
和reject
两个函数,执行回调。
Promise
有三个状态:
pending
:等待中resolved
:已成功rejected
:已失败在 Promise
的状态改变只有两种可能:从 pending
变为 resolved
或者从 pending
变为 rejected
,以下图(引自 Promise 迷你书):
并且须要注意的是一旦状态改变,状态不会再变了,接下来就一直是这个结果。也就是说当咱们在 executor
函数中调用了 resolve
以后,以后调用 reject
就没有效果了,反之亦然。
// 并在 executor 函数中执行业务逻辑 function executor(resolve, reject){ resolve(100); // 以后调用 resolve,reject 都是无效的, // 由于状态已经变为 resolved,不会再改变了 reject(100); } let x1 = new Promise(executor);
每个 promise
都一个 then
方法,这个是当 promise
返回结果以后,须要执行的回调函数,他有两个可选参数:
onFulfilled
:成功的回调;onRejected
:失败的回调;以下图(引自 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
的大体流程:
new Promise
时,须要传递一个 executor
执行器函数,在构造函数中,执行器函数马上执行 executor
执行函数接受两个参数,分别是 resolve
和 reject
Promise
只能从 pending
到 rejected
, 或者从 pending
到 fulfilled
Promise
的状态一旦确认,状态就凝固了,不在改变Promise
都有 then
方法,then
接收两个参数,分别是 Promise
成功的回调 onFulfilled
,和失败的回调 onRejected
then
时,Promise
已经成功,则执行 onFulfilled
,并将 Promise
的值做为参数传递进去;若是 Promise
已经失败,那么执行 onRejected
,并将 Promise
失败的缘由做为参数传递进去;若是 Promise
的状态是 pending
,须要将 onFulfilled
和 onRejected
函数存放起来,等待状态肯定后,再依次将对应的函数执行(观察者模式)then
的参数 onFulfilled
和 onRejected
能够不传,Promise
能够进行值穿透。Promise
能够 then
屡次,Promise
的 then
方法返回一个新的 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
会覆盖 onResolved1
,onRejected2
会覆盖 onRejected1
。
let p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolved('成功了'); }, 1000); }) // 注册多个回调函数 p1.then(onResolved1, onRejected1); p1.then(onResolved2, onRejected2);
接下来咱们更进一步,把这些问题给解决掉。
这一版咱们把状态的概念引入,同时实现链式调用的功能。
上面咱们说到 Promise
有三个状态:pending
、resovled
、rejected
,只能从 pending
转为 resovled
或者 rejected
,并且当状态改变以后,状态就不能再改变了。
status
:用于记录当前 Promise
的状态PENDING
、RESOLVED
、REJECTED
。then
的成功回调定义为一个数组:this.resolvedQueues
与 this.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
的两个参数 onFulfilled
和 onRejected
,直接赋值给了 Promise
的用于保存成功、失败函数回调的实例属性。
如今咱们须要将这两个属性塞入到两个数组中去:resolvedQueues
和 rejectedQueues
。
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) } } }
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
都会返回了一个新的 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
的链式调用跟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
的异步链式调用。
先看一下 then
中 onFulfilled
和 onRejected
返回的值:
// 成功的函数返回 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
接受四个参数:
promise2
是 then
中返回的 promise
;x
是 then
的两个参数 onFulfilled
或者 onRejected
的返回值,类型不肯定,有多是普通值,有多是 thenable
对象;resolve
和 reject
是 promise2
的。当 x
是 Promise
的时,而且他的状态是 Pending
状态,若是 x
执行成功,那么就去递归调用 resolvePromise
这个函数,将 x
执行结果做为 resolvePromise
第二个参数传入;
若是执行失败,则直接调用 promise2
的 reject
方法。
到这里咱们基本上一个完整的 promise
,接下来咱们须要根据 Promises/A+ 来规范一下咱们的 Promise
。
前几版的代码笔者基本上是按照规范来的,这里主要讲几个没有符合规范的点。
then
中onFulfilled
和onRejected
须要异步执行,即放到异步任务中去执行(规范 2.2.4)
咱们须要将 then
中的函数经过 setTimeout
包裹起来,放到一个宏任务中去,这里涉及了 js
的 EventLoop
,你们能够去看看相应的文章,以下:
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
宏任务包裹的。
咱们能够改进一下,使用微任务来包裹 onFulfilled
、onRejected
,经常使用的微任务有 process.nextTick
、MutationObserver
、postMessage
等,咱们这个使用 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"); } // ...
实现方法很简单,咱们监听window
的 message
事件,并在以后立马触发一个 postMessage
事件,这个时候其实 then
中的回调函数已经在微任务队列中了,咱们从新运行一下例子,能够看到输出的顺序变为了 then -> setTimeout
。
固然
Promise
内部实现确定没有这么简单,笔者在这里只是提供一种思路,你们有兴趣能够去研究一波。
重复引用,当x
和promise2
是同样的,那就须要报一个错误,重复应用。(规范 2.3.1)
由于本身等待本身完成是永远都不会有结果的。
const p1 = new MyPromise((resolved, rejected) => { resolved('我 resolved 了'); }); const p2 = p1.then((res) => { return p2; });
大体分为一下这么几条:
x
是一个 Promise
,那么就等待 x
改变状态以后,才算完成或者失败(这个也属于 2.3.3
,由于 Promise
其实也是一个 thenable
对象)x
是一个对象 或者 函数的时候,即 thenable
对象,那就那 x.then
做为 then
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
对象,直接调用 Promise2
的 resolve
,将 x
做为成功的结果;x
是 thenable
对象,会调用 x
的 then
方法,成功后再去调用 resolvePromise
函数,并将执行结果 y
做为新的 x
传入 resolvePromise
,直到这个 x
值再也不是一个 thenable
对象为止;若是失败则直接调用 promise2
的 reject
。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
)规定若是同时调用resolvePromise
和rejectPromise
,或者对同一参数进行了屡次调用,则第一个调用优先,而全部其余调用均被忽略,确保只执行一次改变状态。
咱们在外面定义了一个 called
占位符,为了得到 then
函数有没有执行过相应的改变状态的函数,执行过了以后,就再也不去执行了,主要就是为了知足规范。
若是 x
是 Promise
对象的话,其实当执行了resolve
函数 以后,就不会再执行 reject
函数了,是直接在当前这个 Promise
对象就结束掉了。
当 x
是普通的 thenable
函数的时候,他就有可能同时执行 resolve
和 reject
函数,便可以同时执行 promise2
的 resolve
函数 和 reject
函数,可是其实 promise2
在状态改变了以后,也不会再改变相应的值了。其实也没有什么问题,以下代码:
// thenable 对像 { then: function(resolve, reject) { setTimeout(() => { resolve('我是thenable对像的 resolve'); reject('我是thenable对像的 reject') }) } }
完整的 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
以后,其实编写其实例和静态方法,相对来讲就简单了不少。
其实这个方法就是 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 错误了
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
对象,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(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
实例,包装成一个新的 Promise
实例,
const p = Promise.all([p1, p2, p3]);
p1
、p2
、p3
的状态都变成 fulfilled
,p
的状态才会变成 fulfilled
;p1
、p2
、p3
之中有一个被 rejected
,p
的状态就变成 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 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);
只要 p1
、p2
、p3
之中有一个实例率先改变状态,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
实例做为参数,包装成一个新的 Promise
实例。
const p = Promise.race([p1, p2, p3]);
只有等到全部这些参数实例都返回结果,不论是 fulfilled
仍是 rejected
,并且该方法的状态只可能变成 fulfilled
。
此方法与Promise.all
的区别是all
没法肯定全部请求都结束,由于在all
中,若是有一个被Promise
被rejected
,p
的状态就立马变成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
包装成一个微任务?实不相瞒,想要个赞!
示例代码能够看这里: