带你掌握promise应用及原理(手把手一行一行代码教你手写Promise)
前言
如果你问我有什么方法可以让自己JS的技术活生生地提升一个等级?
那就是手写Promise了!!!
手写Promise 有一个难点就在于有很多地方需要和原生一样严谨,也就是说原生的Promise会考虑很多特殊情况~
我们在实际运用时可能暂时不会碰到这些情况,可是当我们遇到的时候 却不知底层的原理,无法精准定位和解决问题,这就是为什么我们要知道如何手写Promise
如果你问我为什么看了这么多教程还是不懂如何手写Promise,那就是因为这里头有很多细节难点,很少人有人愿意把这些都讲出来,不过我今天就要把这里头的细节一个个给抠出来,所以请大家务必先收藏再观看 ~ 奥力给
手写Promise包含以下知识点 :
- Promise
- Class 类
- 改变this指向 (call、apply和bind)
- 事件循环 Event Loop
- 等
不必担心因为上面的知识点不熟练而无法进行"手写Promise"的学习,因为本文附带 包会套餐 :
- 如果你不太熟悉Promise的话,建议先看我之前发的那篇Promise文章:通俗易懂的Promise知识点总结,检验一下你是否真的完全掌握了promise?
- 如果不知道 类 class 是如何使用的,建议参考我发的这篇文章:ES6新特性 Class 类的全方面理解
- 其他知识点讲解文章,会在文中列出,不用担心,你只需要跟着这篇文中走就完了~
手写之前先简要的复习一下 Promise,现在我们就来一边回忆一边实现Promise吧 ~
如果很熟悉 Promsie 可以跳过下面这一节(不建议)
◾ promise 核心要点
本章节内容其实并不多,而且通俗易懂,建议不太熟悉Promise的同学还是循序渐进的看完本章再逐步学习Promise核心手写~
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
一个 Promise 必然处于以下几种状态之一 :
- 待定 (pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已成功 (fulfilled): 意味着操作成功完成。
- 已拒绝 (rejected): 意味着操作失败。
当 promise 被调用后,它会以处理中状态 (pending) 开始。 这意味着调用的函数会继续执行,而 promise 仍处于处理中直到解决为止,从而为调用的函数提供所请求的任何数据。
被创建的 promise 最终会以被解决状态 (fulfilled) 或 被拒绝状态 (rejected) 结束,并在完成时调用相应的回调函数(传给 then 和 catch)。
◾ 为了让读者尽快对promise有一个整体的理解,我们先来看一段promise的例子 :
let p1 = new Promise((resolve, reject) => { resolve('成功') reject('失败') }) console.log('p1', p1) let p2 = new Promise((resolve, reject) => { reject('失败') resolve('成功') }) console.log('p2', p2) let p3 = new Promise((resolve, reject) => { throw('报错') }) console.log('p3', p3)
输出结果为:
这里包含了四个知识点 :
- 1、执行了resolve(),Promise状态会变成fulfilled,即 已完成状态
- 2、执行了reject(),Promise状态会变成rejected,即 被拒绝状态
- 3、Promise只以第一次为准,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected
- 4、Promise中有throw的话,就相当于执行了reject()
◾ 接下来看下面一段代码,学习新的知识点:
let myPromise1 = new Promise(() => {}); console.log('myPromise1 :>> ', myPromise1); let myPromise2 = new Promise((resolve, reject) => { let a = 1; for (let index = 0; index < 5; index ) { a ; } }) console.log('myPromise2 :>> ', myPromise2) myPromise2.then(() => { console.log("myPromise2执行了then"); })
输出结果为:
这里包含了三个知识点 :
- 1、Promise的初始状态是pending
- 2、Promise里没有执行resolve()、reject()以及throw的话,这个promise的状态也是pending
- 3、基于上一条,pending状态下的promise不会执行回调函数then()
◾ 最后一点:
let myPromise0 = new Promise(); console.log('myPromise0 :>> ', myPromise0);
输出结果:
这个里包含了一个知识点:
一、定义初始结构
- 规定必须给Promise对象传入一个执行函数,否则将会报错。
原生的promise我们一般都会用new来创建实例 :
let promise = new Promise()
所以我们手写的时候可以用构造函数或者class来创建,为了方便代码的整体观看就用class。
如果不知道 类 class 是如何使用的,建议参考我写的这篇文章:ES6新特性 Class 类的全方面理解
把我们手写的Promise命名为myPromise,具体名字可以按自己想法,都可以
首先创建一个myPromise类
class myPromise {}
在new一个promise实例的时候肯定是需要传入参数的
let promise = new Promise(() => {})
不然这个实例用处不大;而这个参数我们知道是一个函数,并且当我们传入这个函数参数的时候,这个函数参数会自动执行。
因此,我们需要在类的构造函数constructor里面添加一个参数,这里就用func来做形参,并且执行一下这个参数
二、实现 resolve 和 reject
class myPromise { constructor(func) { func(); } }
接下来,大家都知道需要为这个函数参数传入它自己的函数,也就是resolve()和reject()
原生的promise里面可以传入resolve和reject两个参数
let promise = new Promise((resolve, reject) => {}) 复制代码
那么我们也得允许手写这边可以传入这两个参数:
class myPromise { constructor(func) { func(resolve, reject); } }
这里这样写明显有一个问题 ,那就是手写这边不知道哪里调用resolve()和reject()这两个参数,毕竟resolve()和reject()还没有定义
因此就需要创造出这两个对象
有一点我们需要知道的是resolve()和reject()也是以函数的形式来执行的,我们在原生promise里也是在resolve和reject后面加括号()来执行的,因此我们可以用类方法的形式来创建这两个函数:
class myPromise { constructor(func) { func(resolve, reject); } resolve() {} reject() {} }
创建这两个方法以后我们发现func里面的两个参数颜色还是原来的颜色,编辑器就是在告诉我们:这两个参数还没有创建噢~
等下,刚刚不是已经创建了吗?
是的,但是我们需要用this来调用自身class的方法,因此我们需要在构造函数里把两个参数前加上this:
class myPromise { constructor(func) { func(this.resolve, this.reject); } resolve() {} reject() {} }
那么这里的resolve()和reject()方法应该如何执行呢?里面应该写什么内容呢?
这就需要用到状态了
1. 管理状态和结果promise有三种状态:分别是pending,fulfilled和rejected
- 初始的时候是pending
- pending可以转为fulfilled状态,但是不能逆转
- pending也可以转为rejected状态,但是也不能逆转
- 这里fulfilled和rejected也不能互转
因此我们需要提前先把这些状态定义好,可以用const来创建外部的固定变量,但是这里为了统一就用static来创建静态属性:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { func(this.resolve, this.reject); } resolve() {} reject() {} }
创建了状态属性以后,还需要为每一个实例添加一个状态属性,在前面讲到得 “Promise 核心要点” 章节,我们已经知道原生Promise用PromiseState这个字段来保存实例的状态属性,这里就也用 this.PromiseState 来保存实例的状态属性,这个状态属性默认就是 待定pending 状态,这样在每一个实例被创建以后就会有自身的状态属性可以进行判断和变动了
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; func(this.resolve, this.reject); } resolve() {} reject() {} }
那么在执行resolve()的时候就需要判断状态是否为 待定 pending,如果是 待定 pending的话就把状态改为 成功 fulfilled:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; func(this.resolve, this.reject); } resolve() { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; } } reject() {} }
同样,为给reject添加参数,并且把参数赋值给实例的PromiseResult属性:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; func(this.resolve, this.reject); } resolve() { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; } } reject() { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECT; } } }
◾ 执行 resolve() 和 reject() 可以传参
现在我们再回忆一下原生Promise ,在执行resolve()或者reject()的时候都是可以传入一个参数,这样我们后面就可以使用这个参数了
let promise = new Promise((resolve, reject) => { resolve('这次一定') })
我们可以把这个结果参数命名为PromiseResult (和原生Promise保持一致),不管是成功还是拒绝的结果,两者选其一,我们让每个实例都有PromiseResult属性,并且给他们都赋值null,这里给空值null是因为执行resolve()或者reject()的时候会给结果赋值:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve, this.reject); } resolve() { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; } } reject() { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECT; } } }
接着我们就可以给resolve()添加参数,并且把参数赋值给实例的PromiseResult属性:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve, this.reject); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject() { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECT; } } }
同样,为给reject()添加参数,并且把参数赋值给实例的PromiseResult属性:
2. this 指向问题
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve, this.reject); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECT; this.PromiseResult = reason; } } }
现在的代码看起来风平浪静的,但很多人会在这里犯错~
大家觉得这里有什么错误?
我们来new一个实例 执行一下代码就知道有没有问题了
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve, this.reject); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECT; this.PromiseResult = reason; } } } // 测试代码 let promise1 = new myPromise((resolve, reject) => { resolve('这次一定'); })
运行上面代码,报错 :
Uncaught TypeError: Cannot read property 'PromiseState ' of undefined
可从报错的信息里面我们貌似发现不了有什么错误,因为PromiseState 属性我们已经创建了,不应该是undefined~
但我们仔细看看resolve()和reject()方法里调用PromiseState ,前面是有this关键字的
resolve(result) { ➡ if (this.PromiseState === myPromise.PENDING) { ➡ this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { ➡ if (this.PromiseState === myPromise.PENDING) { ➡ this.PromiseState = myPromise.REJECT; this.PromiseResult = reason; } }
那么只有一种可能,调用this.PromiseState 的时候并没有调用constructor里的this.PromiseState ,也就是这里的this已经跟丢了~
我们在new一个新实例的时候执行的是constructor里的内容,也就是constructor里的this确实是新实例的,但现在我们是在新实例被创建后再在外部环境下执行resolve()方法的,这里的resolve()看着像是和实例一起执行的,其实不然,也就相当于不在class内部使用这个this,而我们没有在外部定义任何PromiseState 变量,因此这里会报错
解决class的this指向问题一般会用箭头函数,bind或者proxy,在这里我们就可以使用bind来绑定this,只需要在构造函数constructor中的this.resolve和this.reject后加上,.bind(this)就可以了 :
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECT; this.PromiseResult = reason; } } } // 测试代码 let promise1 = new myPromise((resolve, reject) => { resolve('这次一定'); })
如果这里有点蒙圈,不太懂为什么这样写,可以参考我之前写的关于this指向的文章:
- JavaScript 基础系列之 call、apply 和 bind 方法的用法、区别和使用场景
- JavaScript 深入系列之 call、apply 和 bind 方法的模拟实现
我们接着往下写~
对于resolve来说,这里就是给实例的resolve()方法绑定这个this为当前的实例对象,并且执行this.resolve()方法:
对于reject来说,这里就是给实例的reject方法绑定这个this为当前的实例对象,并且执行this.reject方法:
咱们来测试一下代码吧:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } } // 测试代码 let promise1 = new myPromise((resolve, reject) => { resolve('这次一定'); }) console.log(promise1); // myPromise {PromiseState: 'fulfilled', PromiseResult: '这次一定'} let promise2 = new myPromise((resolve, reject) => { reject('下次一定'); }) console.log(promise2); // myPromise {PromiseState: 'rejected', PromiseResult: '下次一定'}
上面是我们手写的 myPromise的执行情况,看看原生Promise的执行情况:
说明执行结果符合我们的预期,是不是觉得离成功又进了一步啦~
那么大家觉得下一步我们要做什么?是不是很多同学觉得需要写then了?那么我们就先来满足想要写then的同学们~
三、实现 then 方法因为then是在创建实例后再进行调用的,因此我们再创建一个 类方法,可千万不要创建在 constructor 里面了~
我想应该有些同学突然失忆,不记得then怎么用了,我们就来稍微写一下原生的then方法:
let promise = new Promise((resolve, reject) => { resolve('这次一定') }) promise.then( result => { console.log(result); }, reason => { console.log(reason.message); } )
then方法可以传入两个参数,这两个参数都是函数,一个是当状态为fulfilled 成功 时执行的代码,另一个是当状态为 rejected 拒绝 时执行的代码。
虽然很多人可能一直只用一个函数参数,但不要忘记这里是两个函数参数。
因此我们就可以先给手写的then里面添加 两个参数:
- 一个是 onFulfilled 表示 “当状态为成功时”
- 另一个是 onRejected 表示 “当状态为拒绝时”
1. 状态不可变
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } then(onFulfilled, onRejected) {} }
这里我们先看看原生 Promise 产生的结果:
let promise = new Promise((resolve, reject) => { resolve('这次一定') reject('下次一定') }) promise.then( result => { console.log('fulfilled', result); }, reason => { console.log('rejected', reason.message); } ) 复制代码
可以看到控制台只显示了一个console.log的结果,证明 Promise 只会执行成功状态 或者 拒绝状态 的其中一个
也就是我们前文讲到的,Promise 只以 第一次为准,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected
因此我们在手写的时候就必须进行判断 :
◾ 如果当前实例的 PromiseState 状态属性为 fulfilled 成功 的话,我们就执行传进来的 onFulfilled 函数,并且为onFulfilled函数传入前面保留的PromiseResult属性值:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } then(onFulfilled, onRejected) { if (this.PromiseState === myProise.FULFILLED) { onFulfilled(this.PromiseResult); } } }
◾ 如果当前实例的 PromiseState 状态属性为 rejected 拒绝 的话,我们就执行传进来的 onRejected 函数,并且为onRejected函数传入前面保留的PromiseResult属性值:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } then(onFulfilled, onRejected) { if (this.PromiseState === myProise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } } }
定义好了判断条件以后,我们就来测试一下代码,也是一样,在实例 上使用then方法:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } then(onFulfilled, onRejected) { if (this.PromiseState === myPromise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } } } // 测试代码 let promise1 = new myPromise((resolve, reject) => { resolve('这次一定'); reject('下次一定'); }) promise1.then( result => { console.log(result) }, reason => { console.log(reason.message) } )
执行上面的测试代码,查看控制台:
可以看到控制台只显示了一个console.log的结果:这次一定 ,证明我们已经实现了 promise的状态不可变
写到这里并没有报错,也就是我们暂时安全了,为什么说暂时安全呢?
因为这里还有很多没有完善的地方,手写Promise的时候,有一个难点就在于有很多地方需要和原生一样严谨,也就是说原生的Promise会考虑很多特殊情况~
我们在实际运用时可能暂时不会碰到这些情况,可是当我们遇到的时候 却不知底层的原理,这就是为什么我们要知道如何手写Promise
接着写
2. 执行异常 throw在new Promise的时候,执行函数里面如果抛出错误,是会触发then方法的第二个参数,即rejected状态的回调函数
也就是在原生的Promise里面,then方法的第二个参数,即rejected状态的回调函数可以把错误的信息作为内容输出出来
到这里,有的同学可能会说,执行异常抛错,不是用catch()方法去接吗?为什么这里又说 是会触发then方法的第二个参数,即rejected状态的回调函数?
那我们就说道说道吧:
catch() 方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
事实上, calling obj.catch(onRejected) 内部calls obj.then(undefined, onRejected)。(这句话的意思是,我们显式使用obj.catch(onRejected),内部实际调用的是obj.then(undefined, onRejected))
Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // 等同于 p.then( null, err=> {console.log(err)} ) // 等同于 p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log("rejected:", err));
◾ 注意看下面的例子 :
const promise = new Promise(function(resolve, reject) { throw new Error('test'); }); promise.catch(function(error) { console.log(error); }); // Error: test
上面代码中,promise抛出一个错误,就被catch()方法指定的回调函数捕获。注意,上面的写法与下面两种写法是等价的。
// 写法一 const promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); } }); promise.catch(function(error) { console.log(error); }); // 写法二 const promise = new Promise(function(resolve, reject) { reject(new Error('test')); }); promise.catch(function(error) { console.log(error); });
比较上面两种写法,可以发现reject()方法的作用,等同于抛出错误。这一点很重要,因为我们手写Promise就是用try/catch来处理异常,用的就是上面的思想。
◾ 一般来说,不要在then()方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { //cb // success }) .catch(function(err) { // error });
上面代码中,第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。因此,建议总是使用catch()方法,而不使用then()方法的第二个参数。
回到正题
原生Promise在new Promise的时候,执行函数里面如果抛出错误,是会触发then方法的第二个参数 (即rejected状态的回调函数),把错误的信息作为内容输出出来:
let promise = new Promise((resolve, reject) => { throw new Error('白嫖不成功'); }) promise.then( result => { console.log('fulfiiled:', result) }, reason => { console.log('rejected:', reason) } )
但是如果我们在手写这边写上同样道理的测试代码,很多人就会忽略这个细节:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; func(this.resolve.bind(this), this.reject.bind(this)); } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } then(onFulfilled, onRejected) { if (this.PromiseState === myPromise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } } } // 测试代码 let promise1 = new myPromise((resolve, reject) => { throw new Error('白嫖不成功'); }) promise1.then( result => { console.log('fulfiiled:', result) }, reason => { console.log('rejected:', reason) } )
我们看看控制台
Uncaught 未捕获
可以发现报错了,没有捕获到错误,没有把内容输出出来
◾ 我们可以在执行resolve()和reject()之前用try/catch进行判断,在构造函数 constructor里面完善代码,判断生成实例的时候是否有报错 :
- 如果没有报错的话,就按照正常执行resolve()和reject()方法
- 如果报错的话,就把错误信息传入给reject()方法,并且直接执行reject()方法
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } then(onFulfilled, onRejected) { if (this.PromiseState === myPromise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } } } // 测试代码 let promise1 = new myPromise((resolve, reject) => { throw new Error('白嫖不成功'); }) promise1.then( result => { console.log('fulfiiled:', result) }, reason => { console.log('rejected:', reason) } )
◾ 注意这里不需要给reject()方法进行this的绑定了,因为这里是直接执行,而不是创建实例后再执行。
▪ func(this.resolve.bind(this), this.reject.bind(this)); 这里的this.reject意思是:把类方法reject()作为参数 传到构造函数constructor 里要执行的func()方法里,只是一个参数,并不执行,只有创建实例后调用reject()方法的时候才执行,此时this的指向已经变了,所以想要正确调用myPromise的reject()方法就要通过.bind(this))改变this指向。
▪ this.reject(error),这里的this.reject(),是直接在构造函数里执行类方法,this指向不变,this.reject()就是直接调用类方法reject(),所以不用再进行this绑定。
◾ 这里考察了this绑定的一个细节:
call、apply和bind都可以改变函数体内部 this 的指向,但是 bind 和 call/apply 有一个很重要的区别:一个函数被 call/apply 的时候,会立即执行函数,但是 bind 会创建一个新函数,不会立即执行。
这就是前面为什么说, this.reject.bind(this)只是作为参数,并没有直接执行的原因了~
回到正文
结合前面的讲解,刷新一下控制台,我们可以看到手写这边已经没有报错了:
3. 参数校验
大家觉得目前代码是不是没问题了?可以进行下一步了?
如果你觉得是的话就又掉坑了~
原生Promise里规定then方法里面的两个参数如果不是函数的话就要被忽略,我们就故意在原生代码这里不传入函数作为参数:
let promise = new Promise((resolve, reject) => { throw new Error('白嫖不成功'); }) promise.then( undefined, reason => { console.log('rejected:', reason) } )
运行以后我们发现在这里执行是没有问题的:
我们再以同样类似的不传 函数参数 的代码应用在 手写代码 上面:
class myPromise { ... } let promise1 = new myPromise((resolve, reject) => { resolve('这次一定'); }) promise1.then( undefined, reason => { console.log('rejected:', reason) } )
大家想想会不会有什么问题?来看看结果会怎样?
结果就是 Uncaught TypeError: onFulfilled is not a function。浏览器帮你报错了,这不是我们想要的~
我们只想要自己来抛出错误,再来看看刚刚的手写then部分:
then(onFulfilled, onRejected) { if (this.PromiseState === myPromise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } }
我们会在里面分别执行成功和拒绝两个参数,可是我们不想修改这里的代码,那么就只能把不是函数的参数改为函数
Promise 规范如果 onFulfilled 和 onRejected 不是函数,就忽略他们,所谓“忽略”并不是什么都不干,对于onFulfilled来说“忽略”就是将value原封不动的返回,对于onRejected来说就是返回reason,onRejected因为是错误分支,我们返回reason应该throw一个Error:
这里我们就可以用 条件运算符,我们在进行if判断之前进行预先判断:
▪ 如果onFulfilled参数是一个函数,就把原来的onFulfilled内容重新赋值给它,如果onFulfilled参数不是一个函数,就将value原封不动的返回
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; if (this.PromiseState === myPromise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } } }
▪ 如果onRejected参数是一个函数,就把原来的onRejected内容重新赋值给它,如果onRejected参数不是一个函数,就throw一个Error
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } } }
现在我们再来测试一下代码:
class myPromise { ... } let promise1 = new myPromise((resolve, reject) => { resolve('这次一定'); }) promise1.then( undefined, reason => { console.log('rejected:', reason) } )
查看控制台,发现没有报错了:
当前实现的完整代码:
四、实现异步1. 添加定时器
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.FULFILLED) { onFulfilled(this.PromiseResult); } if (this.PromiseState === myPromise.REJECTED) { onRejected(this.PromiseResult); } } } 复制代码
在对代码进行一些基本修补以后,我们就可以来进行下一个大功能了,也就是Promise的 异步功能 ✨。
可以说我们在手写的代码里面依旧没有植入异步功能,毕竟最基本的setTimeout我们都没有使用,但是我们必须先了解一下原生Promise的一些运行顺序规则。
在这里我为原生代码添加上步骤信息:
console.log(1); let promise = new Promise((resolve, reject) => { console.log(2); resolve('这次一定'); }) promise.then( result => { console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
我们配合这段原生Promise代码,结合控制台一起看看
输出顺序为:
1 2 3 fulfilled: 这次一定
- 首先执行console.log(1),输出1
- 接着创建promise实例,输出2,因为这里依旧是同步
- 然后碰到resolve的时候,修改结果值
- 到了promise.then会进行异步操作,也就是我们 需要先把执行栈的内容清空,于是就执行console.log(3),输出3
- 接着才会执行promise.then里面的内容,也就是最后输出“fulfilled: 这次一定”
▪ 我们用同样的测试代码应用在 手写代码 上面:
class myPromise { ... } // 测试代码 console.log(1); let promise1 = new myPromise((resolve, reject) => { console.log(2); resolve('这次一定'); }) promise1.then( result => { console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
这次我们发现有些不同了,输出顺序为:
1 2 fulfilled: 这次一定 3
1 和 2 都没有问题,问题就是“fulfilled: 这次一定”和3这里的顺序不对
◾ 其实问题很简单,就是我们刚刚说的 没有设置异步执行
我们二话不说直接给then方法里面添加setTimeout就可以了,需要在进行if判断以后再添加setTimeout,要不然状态不符合添加异步也是没有意义的,然后在setTimeout里执行传入的函数参数:
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } }
我们使用前面的用例重新测试一下代码:
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } } // 测试代码 console.log(1); let promise1 = new myPromise((resolve, reject) => { console.log(2); resolve('这次一定'); }) promise1.then( result => { console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
输出顺序为:
1 2 3 fulfilled: 这次一定
这次的顺序就比较顺眼了~
在这里我们解决异步的方法是给resolve和reject添加setTimeout,但是为什么要这么做呢?
◾ 这就要讲到 Promises/A 规范 了
规范 2.2.4 :
onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
译文:
2.2.4 onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 注1
规范对2.2.4做了注释:
3.1 Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
译文:
3.1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。这个事件队列可以采用“宏任务(macro-task)”机制,比如setTimeout 或者 setImmediate; 也可以采用“微任务(micro-task)”机制来实现, 比如 MutationObserver 或者process.nextTick。 由于 promise 的实施代码本身就是平台代码(译者注: 即都是 JavaScript),故代码自身在处理在处理程序时可能已经包含一个任务调度队列或『跳板』)。
这里我们用的就是规范里讲到的 “宏任务” setTimeout。
2. 回调保存异步的问题真的解决了吗?现在又要进入Promise另一个难点了,大家务必竖起耳朵啦
我们来给原生的Promise里添加setTimeout,使得resolve也异步执行,那么就会出现一个问题了,resolve是异步的,then也是异步的,究竟谁会先被调用呢?
console.log(1); let promise = new Promise((resolve, reject) => { console.log(2); setTimeout(() => { resolve('这次一定'); console.log(4); }); }) promise.then( result => { console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
输出顺序为:
1 2 3 4 fulfilled: 这次一定
特别要注意的是当遇到setTimeout的时候被异步执行了,而resolve('这次一定')没有被马上执行,而是先执行console.log(4),等到then的时候再执行resolve里保存的值。
这里涉及到了浏览器的事件循环,promise.then() 和 setTimeout() 都是异步任务,但实际上异步任务之间并不相同,因此他们的执行优先级也有区别。不同的异步任务被分为两类:微任务 (micro task) 和 宏任务 (macro task)。
- setTimeout()属于宏任务
- promise.then()属于微任务
在一个事件循环中,异步事件返回结果后会被放到一个任务队列中。然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会 查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。
我们只需记住 当 当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
如果想要学习事件循环、微任务和宏任务,可以看我写的这篇文章:JavaScript 深入系列之宏任务、微任务和事件循环 Event Loop
回到正文
我们用同样的代码应用到手写的部分:
class myPromise { ... } // 测试代码 console.log(1); let promise1 = new myPromise((resolve, reject) => { console.log(2); setTimeout(() => { resolve('这次一定'); console.log(4); }); }) promise1.then( result => { console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
控制台输出:
1 2 3 4
可以发现 fulfilled: 这次一定 并没有输出
我们可以先猜测一下,没有输出的原因很可能是因为then方法没有被执行,看看then方法里面是根据条件判断来执行代码的:
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } }
也就是说很可能没有符合的条件,再换句话说可能没有符合的状态
那么我们就在三个位置分别输出当前的状态,这样分别来判断哪个位置出了问题:
class myPromise { ... } // 测试代码 console.log(1); let promise1 = new myPromise((resolve, reject) => { console.log(2); setTimeout(() => { console.log('A',promise1.PromiseState); resolve('这次一定'); console.log('B',promise1.PromiseState); console.log(4); }); }) promise1.then( result => { console.log('C',promise1.PromiseState); console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
输出结果为:
1 2 3 A pending B fulfilled 4
发现只有两组状态被输出,这两组都在console.log(4)前被输出,证明setTimeout里面的状态都被输出了,只有then里面的状态没有被输出
这基本就可以确定是因为then里的状态判断出了问题
这里涉及到事件循环,我们详细解读一下:
▪ 首先,执行console.log(1),输出1
▪ 第二步,创建promise,执行函数体里的console.log(2),输出2
▪ 第三步,遇到setTimeout,setTimeout是宏任务,将setTimeout加入宏任务队列,等待执行
▪ 第四步,遇到promise.then(),promise.then()是微任务,将promise.then()加入微任务队列,等待执行
▪ 第五步,执行console.log(3),输出3,此时当前执行栈已经清空
▪ 第六步,当前执行栈已经清空,先执行微任务队列的任务 promise.then(),发现promise的状态并没有改变,还是pending,所以没有输出。状态并没有改变的原因是:resolve('这次一定')是在setTimeout里的,但此时还没开始执行setTimeout,因为setTimeout是宏任务,宏任务在微任务后面执行
▪ 第七步,微任务队列已经清空,开始执行宏任务 setTimeout:
setTimeout(() => { console.log('A',promise1.PromiseState); resolve('这次一定'); console.log('B',promise1.PromiseState); console.log(4); });
▪ 第八步,执行 console.log('A',promise1.PromiseState),此时promise状态还没发生变化,还是pending,所以输出 A pending
▪ 第九步,执行 resolve('这次一定');,改变promise的状态为fulfilled
▪ 第十步,执行 console.log('B',promise1.PromiseState),输出 B fulfilled
▪ 第十一步,执行 console.log(4),输出4
这里暂且认为我们写的promise.then()和原生一样,方便理解
◾ 分析完上面的代码,我们知道了,因为先执行了then方法,但发现这个时候状态依旧是 pending,而我们手写部分没有定义pending待定状态的时候应该做什么,因此就少了fulfilled: 这次一定 这句话的输出
所以我们就 直接给then方法里面添加待定状态的情况就可以了,也就是用if进行判断:
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.PENDING) { } if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } }
◾ 但是问题来了,当then里面判断到 pending 待定状态时我们要干什么?
因为这个时候resolve或者reject还没获取到任何值,因此我们必须让then里的函数稍后再执行,等resolve执行了以后,再执行then
为了保留then里的函数,我们可以创建 数组 来 保存函数。
为什么用 数组 来保存这些回调呢?因为一个promise实例可能会多次 then,也就是经典的 链式调用,而且数组是先入先出的顺序
在实例化对象的时候就让每个实例都有这两个数组:
- onFulfilledCallbacks :用来 保存成功回调
- onRejectedCallbacks :用来 保存失败回调
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } }
◾ 接着就完善then里面的代码,也就是当判断到状态为 pending 待定时,暂时保存两个回调,也就是说暂且把then里的两个函数参数分别放在两个数组里面:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}; onRejected = typeof onRejected === 'function' ? onRejected : () => {}; if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } }
◾ 数组里面放完函数以后,就可以完善resolve和reject的代码了
在执行resolve或者reject的时候,遍历自身的callbacks数组,看看数组里面有没有then那边 保留 过来的 待执行函数,然后逐个执行数组里面的函数,执行的时候会传入相应的参数:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; this.onFulfilledCallbacks.forEach(callback => { callback(result) }) } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } }
完善好代码后,让我们再来测试以下刚才的实例:
class myPromise { ... } // 测试代码 console.log(1); let promise1 = new myPromise((resolve, reject) => { console.log(2); setTimeout(() => { console.log('A', promise1.PromiseState); resolve('这次一定'); console.log('B', promise1.PromiseState); console.log(4); }); }) promise1.then( result => { console.log('C', promise1.PromiseState); console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
输出结果:
1 2 3 A pending C fulfilled fulfilled: 这次一定 B fulfilled 4
从上面的结果我们可以看到 fulfilled: 这次一定 打印出来啦,promise1.then()方法也正常执行,打印出了当前的状态:B fulfilled
但是
细心的同学可能已经发现了,代码输出顺序还是不太对,原生Promise中,fulfilled: 这次一定 是最后输出的
◾ 这里有一个很多人忽略的小细节,resolve 和 reject 是要在 事件循环末尾 执行的,因此我们就 给 resolve 和 reject 里面加上 setTimeout 就可以了
我们在判断完 promise 状态后再加 setTimeout :
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; this.onFulfilledCallbacks.forEach(callback => { callback(result) }) }); } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) }); } } }
为什么不像下面这样呢?
resolve(result) { // bad setTimeout(() => { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; this.onFulfilledCallbacks.forEach(callback => { callback(result) }) } }); } reject(reason) { // bad setTimeout(() => { if (this.PromiseState === myPromise.PENDING) { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) } }); }
因为在一个promise里,可能有的人会不注意同时用了resolve() 和 reject() ,像这样:
let promise = new Promise((resolve, reject) => { resolve('这次一定') reject('下次一定') })
▪ 如果把 setTimeout 放到 if 判断之前,那岂不是就算状态不满足条件,我们也要开启一个定时器,上面的情况,同时用了resolve() 和 reject(),那我们是不是要同时开启两个定时器?如果在一个promise里多次使用 resolve() 和 reject() 方法,那岂不是要开启更多的无用的定时器?
▪ 如果先判断状态再添加定时器,这样我们就 只会满足条件后才开启一个定时器,相比上面的情况,这样的 开销 就小了很多
聊完细节,我们回到正文,检验一下这次是否能行:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; this.onFulfilledCallbacks.forEach(callback => { callback(result) }) }); } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) }); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } } // 测试代码 console.log(1); let promise1 = new myPromise((resolve, reject) => { console.log(2); setTimeout(() => { console.log('A', promise1.PromiseState); resolve('这次一定'); console.log('B', promise1.PromiseState); console.log(4); }); }) promise1.then( result => { console.log('C', promise1.PromiseState); console.log('fulfilled:', result); }, reason => { console.log('rejected:', reason) } ) console.log(3);
输出顺序:
1 2 3 A pending B pending 4 C fulfilled fulfilled: 这次一定
可以看到最后输出 fulfilled: 这次一定 ,和原生Promise顺序一致!
到这里我们已经完成了 promise的回调保存,已经越来越接近胜利了
3. 验证 then 方法多次调用Promise 的 then 方法可以被多次调用。
用一个 ,来验证一下我们写的promise then 方法是否可以多次调用:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; this.onFulfilledCallbacks.forEach(callback => { callback(result) }) }); } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) }); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } } // 测试代码 const promise = new myPromise((resolve, reject) => { setTimeout(() => { resolve('success') }, 2000); }) promise.then(value => { console.log(1) console.log('resolve', value) }) promise.then(value => { console.log(2) console.log('resolve', value) }) promise.then(value => { console.log(3) console.log('resolve', value) })
运行上面 ,输出结果
1 resolve success 2 resolve success 3 resolve success
所有 then 中的回调函数都已经执行
说明我们当前的代码,已经可以实现 then 方法的多次调用✨
完美,继续
五、实现 then 方法的链式调用我们常常用到 new Promise().then().then(),这就是链式调用,用来解决回调地狱
举个例子
let p1 = new Promise((resolve, reject) => { resolve(10) }) p1.then(res => { console.log('fulfilled', res); return 2 * res }).then(res => { console.log('fulfilled', res) })
输出:
fulfilled 100 fulfilled 200
再举一个例子 :
const p2 = new Promise((resolve, reject) => { resolve(100) }) p2.then(res => { console.log('fulfilled', res); return new Promise((resolve, reject) => resolve(3 * res)) }).then(res => { console.log('fulfilled', res) })
输出:
fulfilled 100 fulfilled 300
我们先试一下当前的myPromise是否可以实现链式调用:
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; this.onFulfilledCallbacks.forEach(callback => { callback(result) }) }); } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) }); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } } } // 测试代码 let p1 = new myPromise((resolve, reject) => { resolve(10) }) p1.then(res => { console.log('fulfilled', res); return 2 * res }).then(res => { console.log('fulfilled', res) })
毫无疑问在控制台里面是会报错的,提示 then 方法没有定义:
Uncaught TypeError: Cannot read property 'then' of undefined
Promise.prototype.then() 方法返回一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
1. Promises/A 规范的理解◾ 想要实现then方法的链式调用,就必须彻底搞懂then方法,这里我们参考 Promises/A 规范
规范在2.2.7中这样描述 :
◾ 2.2.7 then 方法必须返回一个 promise 对象
promise2 = promise1.then(onFulfilled, onRejected);
- 2.2.7.1 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
- 2.2.7.2 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
- 2.2.7.3 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
- 2.2.7.4 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
理解上面的“返回”部分非常重要,即:不论 promise1 被 reject 还是被 resolve 时 promise2 都会执行 Promise 解决过程:[[Resolve]](promise2, x),只有出现异常时才会被 rejected。
注意 2.2.7.1 :
If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
即:如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
规范在 2.3 中详细描述 Promise 解决过程 The Promise Resolution Procedure
译过来 :
◾ 2.3 Promise 解决过程
Promise 解决过程 是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。
这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promises/A 协议的 then 方法即可;这同时也使遵循 Promises/A 规范的实现可以与那些不太规范但可用的实现能良好共存。
运行 [[Resolve]](promise, x) 需遵循以下步骤:
▪ 2.3.1 x 与 promise 相等
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
▪ 2.3.2 x 为 Promise
如果 x 为 Promise ,则使 promise 接受 x 的状态
- 2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
- 2.3.2.2 如果 x 处于执行态,用相同的值执行 promise
- 2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise
▪ 2.3.3 x 为对象或函数
如果 x 为对象或者函数:
- 2.3.3.1 把 x.then 赋值给 then
- 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
- 2.3.3.3 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise: 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y) 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 2.3.3.3.4 如果调用 then 方法抛出了异常 e: 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之 2.3.3.3.4.2 否则以 e 为据因拒绝 promise 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise
▪ 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise。
2. Promises/A 规范的总结基于规范的描述,我们得到以下几点:
◾ 1. then方法本身会返回一个新的Promise对象,返回一个新的Promise以后它就有自己的then方法,这样就能实现无限的链式
◾ 2. 不论 promise1 被 resolve() 还是被 reject() 时 promise2 都会执行 Promise 解决过程:[[Resolve]](promise2, x)
在手写这里我们把这个 Promise 解决过程:[[Resolve]](promise2, x) 命名为 resolvePromise() 方法,参数为 (promise2, x, resolve, reject) 即:
function resolvePromise(promise2, x, resolve, reject) {}
resolvePromise()各参数的意义:
/** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) {}
其实,这个resolvePromise(promise2, x, resolve, reject) 即 Promise 解决过程:[[Resolve]](promise2, x) 就是对resolve()、reject() 进行改造增强, 针对resolve()和reject()中不同值情况 进行处理。
resolve()和reject() 返回的 x 值的几种情况:
- 普通值
- Promise对象
- thenable对象/函数
下面我们就根据总结的两点,结合 Promises/A 规范 来实现 then 方法的链式调用
3. then 方法返回一个新的Promise◾ 2.2.7规范 then 方法必须返回一个 promise 对象
我们在then方法里面返回一个 新的手写Promise实例**,再把原来的代码复制上去:
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; const promise2 = new myPromise((resolve, reject) => { if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { onFulfilled(this.PromiseResult); }); } else if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { onRejected(this.PromiseResult); }); } else if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } }) return promise2 } }
◾ 2.2.7.1规范 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; const promise2 = new myPromise((resolve, reject) => { if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); }); } else if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); }); } else if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } }) return promise2 } } /** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) {}
我们在 myPromise 类外面声明了一个 Promise 解决过程:
function resolvePromise(promise2, x, resolve, reject) { }
resolvePromise() 具体方法我们后面会补充~
◾ 2.2.7.2 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; const promise2 = new myPromise((resolve, reject) => { if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); // 捕获前面onFulfilled中抛出的异常 } }); } else if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } else if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } }) return promise2 } } /** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) {}
◾ fulfilled 和 rejected 状态处理完,不要忘了 pending 状态的情况
我们在 pending 状态保存的 resolve() 和 reject() 回调也要符合 2.2.7.1 和 2.2.7.2 规范:
如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行 Promise 解决过程:[[Resolve]](promise2, x)
如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; const promise2 = new myPromise((resolve, reject) => { if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); // 捕获前面onFulfilled中抛出的异常 } }); } else if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } else if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = this.onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }) return promise2 } } /** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) {}
搞定 then 方法
下面我们开始着手写 promise 解决过程 resolvePromise(promise2, x, resolve, reject)
六、实现 resolvePromise 方法◾ 2.3.1 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
class myPromise { ... then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => {}; onRejected = typeof onRejected === 'function' ? onRejected : () => {}; const promise2 = new myPromise((resolve, reject) => { if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); // 捕获前面onFulfilled中抛出的异常 } }); } else if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } else if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = this.onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }) return promise2 } } /** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { // 如果从onFulfilled或onRejected中返回的 x 就是promise2,会导致循环引用报错 if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); } }
例如下面这种情况
const promise = new Promise((resolve, reject) => { resolve(100) }) const p1 = promise.then(value => { console.log(value) return p1 })
使用原生 Promise 执行这个代码,会报类型错误:
◾ 2.3.2 如果 x 为 Promise ,则使 promise 接受 x 的状态
class myPromise { ... } /** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); } // 2.3.2 如果 x 为 Promise ,则使 promise2 接受 x 的状态 if (x instanceof myPromise) { if (x.PromiseState === myPromise.PENDING) { /** * 2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝 * 注意"直至 x 被执行或拒绝"这句话, * 这句话的意思是:x 被执行x,如果执行的时候拿到一个y,还要继续解析y */ x.then(y => { resolvePromise(promise2, y, resolve, reject) }, reject); } else if (x.PromiseState === myPromise.FULFILLED) { // 2.3.2.2 如果 x 处于执行态,用相同的值执行 promise resolve(x.PromiseResult); } else if (x.PromiseState === myPromise.REJECTED) { // 2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise reject(x.PromiseResult); } } }
马上就要成功啦,还有最后一条
◾ 2.3.3 如果 x 为对象或者函数 ◾ 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
在判断x是对象或函数时,x 不能是 null,因为 typeof null的值也为 object
我们应该显式的声明 x != null,这样 当 x 为 null 时,直接执行resolve(x),否则,如果不这样不声明,x 为 null 时就会走到catch然后reject,这不是我们要的,所以需要检测下null:
if (x != null && ((typeof x === 'object' || (typeof x === 'function'))))
◾ 2.3.3 和 2.3.4 规范实现如下:
class myPromise { ... } /** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); } // 2.3.2 如果 x 为 Promise ,则使 promise2 接受 x 的状态 if (x instanceof myPromise) { if (x.PromiseState === myPromise.PENDING) { /** * 2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝 * 注意"直至 x 被执行或拒绝"这句话, * 这句话的意思是:x 被执行x,如果执行的时候拿到一个y,还要继续解析y */ x.then(y => { resolvePromise(promise2, y, resolve, reject) }, reject); } else if (x.PromiseState === myPromise.FULFILLED) { // 2.3.2.2 如果 x 处于执行态,用相同的值执行 promise resolve(x.PromiseResult); } else if (x.PromiseState === myPromise.REJECTED) { // 2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise reject(x.PromiseResult); } } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) { // 2.3.3 如果 x 为对象或函数 try { // 2.3.3.1 把 x.then 赋值给 then var then = x.then; } catch (e) { // 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise return reject(e); } /** * 2.3.3.3 * 如果 then 是函数,将 x 作为函数的作用域 this 调用之。 * 传递两个回调函数作为参数, * 第一个参数叫做 `resolvePromise` ,第二个参数叫做 `rejectPromise` */ if (typeof then === 'function') { // 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 let called = false; // 避免多次调用 try { then.call( x, // 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y) y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, // 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise r => { if (called) return; called = true; reject(r); } ) } catch (e) { /** * 2.3.3.3.4 如果调用 then 方法抛出了异常 e * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之 */ if (called) return; called = true; /** * 2.3.3.3.4.2 否则以 e 为据因拒绝 promise */ reject(e); } } else { // 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise resolve(x); } } else { // 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise return resolve(x); } }
打完收工✨✨✨✨
resolvePromise()方法 完整代码:
七、完整的 Promises/A 实现
/** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); } // 2.3.2 如果 x 为 Promise ,则使 promise2 接受 x 的状态 if (x instanceof myPromise) { if (x.PromiseState === myPromise.PENDING) { /** * 2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝 * 注意"直至 x 被执行或拒绝"这句话, * 这句话的意思是:x 被执行x,如果执行的时候拿到一个y,还要继续解析y */ x.then(y => { resolvePromise(promise2, y, resolve, reject) }, reject); } else if (x.PromiseState === myPromise.FULFILLED) { // 2.3.2.2 如果 x 处于执行态,用相同的值执行 promise resolve(x.PromiseResult); } else if (x.PromiseState === myPromise.REJECTED) { // 2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise reject(x.PromiseResult); } } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) { // 2.3.3 如果 x 为对象或函数 try { // 2.3.3.1 把 x.then 赋值给 then var then = x.then; } catch (e) { // 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise return reject(e); } /** * 2.3.3.3 * 如果 then 是函数,将 x 作为函数的作用域 this 调用之。 * 传递两个回调函数作为参数, * 第一个参数叫做 `resolvePromise` ,第二个参数叫做 `rejectPromise` */ if (typeof then === 'function') { // 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 let called = false; // 避免多次调用 try { then.call( x, // 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y) y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, // 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise r => { if (called) return; called = true; reject(r); } ) } catch (e) { /** * 2.3.3.3.4 如果调用 then 方法抛出了异常 e * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之 */ if (called) return; called = true; /** * 2.3.3.3.4.2 否则以 e 为据因拒绝 promise */ reject(e); } } else { // 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise resolve(x); } } else { // 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise return resolve(x); } }
到这里我们的myPromsie已经完成了 Promises/A 规范
ES6的官方Promise还有很多API,但这些都不在Promises/A 里面
这里为大家提供了两个完整的 Promises/A 实现版本:
1. 清爽简洁 无注释版
- 清爽简洁 无注释版
- 按步分析 注释加持版
完整的 Promises/A 实现 (清爽简洁 无注释版):
完整版的代码较长,这里如果看不清楚的可以去我的GitHub上看,我专门维护了一个 手写 Promsie 的仓库:github.com/yuanyuanbyt…
2. 按步分析 注释加持版
class myPromise { static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; constructor(func) { this.PromiseState = myPromise.PENDING; this.PromiseResult = null; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; try { func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { this.reject(error) } } resolve(result) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; this.onFulfilledCallbacks.forEach(callback => { callback(result) }) }); } } reject(reason) { if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) }); } } then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; let promise2 = new myPromise((resolve, reject) => { if (this.PromiseState === myPromise.FULFILLED) { setTimeout(() => { try { let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); } else if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } else if (this.PromiseState === myPromise.PENDING) { this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }) return promise2 } } function resolvePromise(promise2, x, resolve, reject) { if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); } if (x instanceof myPromise) { if (x.PromiseState === myPromise.PENDING) { x.then(y => { resolvePromise(promise2, y, resolve, reject) }, reject); } else if (x.PromiseState === myPromise.FULFILLED) { resolve(x.PromiseResult); } else if (x.PromiseState === myPromise.REJECTED) { reject(x.PromiseResult); } } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) { try { var then = x.then; } catch (e) { return reject(e); } if (typeof then === 'function') { let called = false; try { then.call( x, y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, r => { if (called) return; called = true; reject(r); } ) } catch (e) { if (called) return; called = true; reject(e); } } else { resolve(x); } } else { return resolve(x); } }
完整的 Promises/A 实现 (按步分析 注释加持版):
完整版的代码较长,这里如果看不清楚的可以去我的GitHub上看,我专门维护了一个 手写 Promsie 的仓库:github.com/yuanyuanbyt…
八、Promise A 测试
class myPromise { // 为了统一用static创建静态属性,用来管理状态 static PENDING = 'pending'; static FULFILLED = 'fulfilled'; static REJECTED = 'rejected'; // 构造函数:通过new命令生成对象实例时,自动调用类的构造函数 constructor(func) { // 给类的构造方法constructor添加一个参数func this.PromiseState = myPromise.PENDING; // 指定Promise对象的状态属性 PromiseState,初始值为pending this.PromiseResult = null; // 指定Promise对象的结果 PromiseResult this.onFulfilledCallbacks = []; // 保存成功回调 this.onRejectedCallbacks = []; // 保存失败回调 try { /** * func()传入resolve和reject, * resolve()和reject()方法在外部调用,这里需要用bind修正一下this指向 * new 对象实例时,自动执行func() */ func(this.resolve.bind(this), this.reject.bind(this)); } catch (error) { // 生成实例时(执行resolve和reject),如果报错,就把错误信息传入给reject()方法,并且直接执行reject()方法 this.reject(error) } } resolve(result) { // result为成功态时接收的终值 // 只能由pedning状态 => fulfilled状态 (避免调用多次resolve reject) if (this.PromiseState === myPromise.PENDING) { /** * 为什么resolve和reject要加setTimeout? * 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行. * 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。 * 这个事件队列可以采用“宏任务(macro-task)”机制,比如setTimeout 或者 setImmediate; 也可以采用“微任务(micro-task)”机制来实现, 比如 MutationObserver 或者process.nextTick。 */ setTimeout(() => { this.PromiseState = myPromise.FULFILLED; this.PromiseResult = result; /** * 在执行resolve或者reject的时候,遍历自身的callbacks数组, * 看看数组里面有没有then那边 保留 过来的 待执行函数, * 然后逐个执行数组里面的函数,执行的时候会传入相应的参数 */ this.onFulfilledCallbacks.forEach(callback => { callback(result) }) }); } } reject(reason) { // reason为拒绝态时接收的终值 // 只能由pedning状态 => rejected状态 (避免调用多次resolve reject) if (this.PromiseState === myPromise.PENDING) { setTimeout(() => { this.PromiseState = myPromise.REJECTED; this.PromiseResult = reason; this.onRejectedCallbacks.forEach(callback => { callback(reason) }) }); } } /** * [注册fulfilled状态/rejected状态对应的回调函数] * @param {function} onFulfilled fulfilled状态时 执行的函数 * @param {function} onRejected rejected状态时 执行的函数 * @returns {function} newPromsie 返回一个新的promise对象 */ then(onFulfilled, onRejected) { /** * 参数校验:Promise规定then方法里面的两个参数如果不是函数的话就要被忽略 * 所谓“忽略”并不是什么都不干, * 对于onFulfilled来说“忽略”就是将value原封不动的返回, * 对于onRejected来说就是返回reason, * onRejected因为是错误分支,我们返回reason应该throw一个Error */ onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value; onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; }; // 2.2.7规范 then 方法必须返回一个 promise 对象 let promise2 = new myPromise((resolve, reject) => { if (this.PromiseState === myPromise.FULFILLED) { /** * 为什么这里要加定时器setTimeout? * 2.2.4规范 onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 注1 * 这里的平台代码指的是引擎、环境以及 promise 的实施代码。 * 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。 * 这个事件队列可以采用“宏任务(macro-task)”机制,比如setTimeout 或者 setImmediate; 也可以采用“微任务(micro-task)”机制来实现, 比如 MutationObserver 或者process.nextTick。 */ setTimeout(() => { try { // 2.2.7.1规范 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x),即运行resolvePromise() let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { // 2.2.7.2 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e reject(e); // 捕获前面onFulfilled中抛出的异常 } }); } else if (this.PromiseState === myPromise.REJECTED) { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e) } }); } else if (this.PromiseState === myPromise.PENDING) { // pending 状态保存的 resolve() 和 reject() 回调也要符合 2.2.7.1 和 2.2.7.2 规范 this.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(this.PromiseResult); resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e); } }); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(this.PromiseResult); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }); }); } }) return promise2 } } /** * 对resolve()、reject() 进行改造增强 针对resolve()和reject()中不同值情况 进行处理 * @param {promise} promise2 promise1.then方法返回的新的promise对象 * @param {[type]} x promise1中onFulfilled或onRejected的返回值 * @param {[type]} resolve promise2的resolve方法 * @param {[type]} reject promise2的reject方法 */ function resolvePromise(promise2, x, resolve, reject) { // 2.3.1规范 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); } // 2.3.2规范 如果 x 为 Promise ,则使 promise2 接受 x 的状态 if (x instanceof myPromise) { if (x.PromiseState === myPromise.PENDING) { /** * 2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝 * 注意"直至 x 被执行或拒绝"这句话, * 这句话的意思是:x 被执行x,如果执行的时候拿到一个y,还要继续解析y */ x.then(y => { resolvePromise(promise2, y, resolve, reject) }, reject); } else if (x.PromiseState === myPromise.FULFILLED) { // 2.3.2.2 如果 x 处于执行态,用相同的值执行 promise resolve(x.PromiseResult); } else if (x.PromiseState === myPromise.REJECTED) { // 2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise reject(x.PromiseResult); } } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) { // 2.3.3 如果 x 为对象或函数 try { // 2.3.3.1 把 x.then 赋值给 then var then = x.then; } catch (e) { // 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise return reject(e); } /** * 2.3.3.3 * 如果 then 是函数,将 x 作为函数的作用域 this 调用之。 * 传递两个回调函数作为参数, * 第一个参数叫做 `resolvePromise` ,第二个参数叫做 `rejectPromise` */ if (typeof then === 'function') { // 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 let called = false; // 避免多次调用 try { then.call( x, // 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y) y => { if (called) return; called = true; resolvePromise(promise2, y, resolve, reject); }, // 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise r => { if (called) return; called = true; reject(r); } ) } catch (e) { /** * 2.3.3.3.4 如果调用 then 方法抛出了异常 e * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之 */ if (called) return; called = true; /** * 2.3.3.3.4.2 否则以 e 为据因拒绝 promise */ reject(e); } } else { // 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise resolve(x); } } else { // 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise return resolve(x); } }
如何证明我们写的myPromise就符合 Promises/A 规范呢?
跑一下 Promise A 测试 就好啦~
1. 安装官方测试工具我们使用Promises/A 官方的测试工具 promises-aplus-tests 来对我们的myPromise进行测试
安装 promises-aplus-tests:
npm install promises-aplus-tests -D
安装完测试工具后的项目目录:
2. 使用 ES6 Module 对外暴露 myPromise 类
3. 实现静态方法 deferred
class myPromise { ... } function resolvePromise(promise2, x, resolve, reject) { ... } module.exports = myPromise;
要使用 promises-aplus-tests 这个工具测试,必须实现一个静态方法deferred(),官方对这个方法的定义如下:
意思就是:
我们要给自己手写的myPromise上实现一个静态方法deferred(),该方法要返回一个包含{ promise, resolve, reject }的对象:
- promise 是一个处于pending状态的 Promsie。
- resolve(value) 用value解决上面那个promise
- reject(reason) 用reason拒绝上面那个promise
deferred()的实现如下:
4. 配置 package.json
class myPromise { ... } function resolvePromise(promise2, x, resolve, reject) { ... } myPromise.deferred = function () { let result = {}; result.promise = new myPromise((resolve, reject) => { result.resolve = resolve; result.reject = reject; }); return result; } module.exports = myPromise;
我们实现了deferred 方法,也通过 ES6 Module 对外暴露了myPromise,最后配置一下package.json就可以跑测试啦~
新建一个 package.json ,配置如下:
// package.json { "devDependencies": { "promises-aplus-tests": "^2.1.2" }, "scripts": { "test": "promises-aplus-tests myPromise" } }
项目目录:
准备工作已就绪
激动人心的时刻马上就要到啦,嘿嘿
5. 完美通过官方872个测试用例执行测试命令:
npm run test
肯定都等不及了吧~ 快来看看我们的测试结果吧,走起
Promises/A 测试总共872用例,我们写的Promise完美通过了所有用例:
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com