从0到1手写一个符合Promise A+规范的Promise类
本篇文章,我会一步一步带大家从头实现一个符合Promise A+ 规范的 Promise类,认真阅读本篇文章,并自己跟着步骤实现一遍后,相信你会对Promise有更深一步的了解。你将会学习到下面几点内容。
- promise的出现,解决前端的哪些问题
- promise的执行机制
.then(...)
,.catch()
- promise为什么可以链式执行
.then().then().then()
- 手写Promise.race()
和
Promise.all()`
术语
- promise 是一个有 then 方法的对象或者是函数,行为遵循
Promise A+
规范。 - thenable 是一个有 then 方法的对象或者是函数。
- value 是 promise 状态成功时的值,也就是resolve的参数,包括各种数据类型,也包括undefined/thenable或者是promise
- reason 是 promise 状态失败时的值,也就是reject的参数,表示拒绝的原因
- exception 是一个使用throw抛出的异常值
规范
Promise States
promise有三种状态,要注意他们之间的流转关系
pending
1.1 初始状态,可改变
1.2 一个promise在resolve或者rejected前都处于这个状态
1.3 可以通过 resolve -> fulfilled 状态
1.4 可以通过 reject -> rejected 状态
fulfilled
1.1 最终态,不可改变
1.2 一个promise被resolve后改变成这个状态
1.3 必须拥有一个value值,
(注意:如果直接resolve(),那么这个value值是undefined )
rejected
1.1 最终态,不可改变
1.2 一个promise被rejected后改变成这个状态
1.3 必须拥有一个reason值,
(注意:如果直接reject(),那么这个value值是undefined )
Tips:总结一下就是,promise的状态流转是这样的
pending -> resolve(value) -> fulfilled
pending -> reject(reason) -> rejected
一步一步实现promise
第一步:先有一个类MyPromise
肯定需要先有一个类,因为平时使用promise的时候,都是 new Promse(...),所以第一步,先声明一个类,这里我们叫MyPromise
,在构造方法中,有三个属性,status
,value
,reason
// 声明状态常量
const PENDING = 'pending' // 默认状态
const FULFILLED = 'fulfilled' // resolve()之后的状态
const REJECTED = 'rejected' // reject() 之后的状态
class MyPromise {
constructor() {
this.status = PENDING // 默认状态
this.value = null
this.reason = null
}
}
第二步:构造器增加参数fn
是不是少了点啥,平时使用promise时,new Promise((resolve, reject)=>{})
,构造方法接收的参数是一个函数,没有错,少的就是参数,我们把参数加上
class MyPromise {
constructor(fn) {
this.status = PENDING // 默认状态
this.value = null
this.reason = null
// 在Promise A+ 规范中 在初始化promise的时候,就需要执行这个函数,所以我们直接执行一下
fn()
}
}
在执行函数fn
时,需要处理一下参数resolve
和reject
,并且需要注意执行fn
的时候,并不确定这个函数里面有什么逻辑,可能会有错误,所以应该使用try...catch...包裹一下,改造后如下
class MyPromise {
constructor(fn) {
this.status = PENDING // 默认状态
this.value = null
this.reason = null
/**
* 注意:在初始化promise的时候,就需要执行这个函数,并且有任何报错都要通过reject抛出去
* 所以,执行的时候用try...catch...包裹一下,使用bind绑定当前的this,防止出现乱起八糟的情况
*/
try {
fn(this.resolve.bind(this), this.reject.bind(this))
} catch (e) {
this.reject(e) // 报错的时候,直接reject
}
}
// 状态由pending->fulfilled
resolve(value) {}
// 状态由pending->rejected
reject(reason) {}
}
第三步:resolve
和reject
方法
上面我们已经处理过了入参fn
,接下来就应该着重看一下fn
的两个参数resolve
和reject
,这两个参数,其实就是promise的两个回调函数,用来改变状态和返回结果,我们来写一下这两个函数的逻辑
class MyPromise {
constructor(fn) {/** ... */}
// 参数value是外界给传过来的
/**
* @description promise的resolve,成功时执行
* 做了两件事
* 1、设置自己的 value
* 2、将状态改为 FULFILLED
* 状态有两种流转方式,所以只有当前状态是 PENDING 时才允许改变,一旦改变不可逆
* pending -> resolve(value) -> fulfilled
* pending -> reject(reason) -> rejected
*
* @param {*} value 从外面传过来的值,可以是任意类型
*/
resolve(value) {
if (this.status === PENDING) {
this.value = value
this.status = FULFILLED
}
}
// 参数reason是外界给传过来的
reject(reason) {
if (this.status === PENDING) {
this.reason = reason
this.status = REJECTED
}
}
/**
* 比如我们封装一个jQuery ajax请求时(就简单的做个比方)
* getData(){
* return new Promise((resolve, reject)=>{
* $.ajax(
* url: 'xxx',
* success: function(data){
* // 这里就拿到了这个data,然后就可以执行promise的回调resolve将这个结果返回出去
* // 这时就触发了Promise的resolve回调,
* resolve(data)
* },
* error: function(error){
* // 失败就可以reject出去这个错误
* reject(error)
* },
*
* )
* })
* }
*
*/
}
第四步:then(onFulfilled, onRejected)
方法
接下来我们就开始研究一下这个then方法了,在平常使用promise时,return new Promise().then(fn1,fn2)
,.then
方法接收两个参数,第一个参数是成功时的回调,第二个是失败时的回调,注意,这两个回调只会执行一个(咱举个例子试试)
不管怎么试,最后的结果都是一样的,这两个回调,只会执行一个,所以咱开始写一下then函数
class MyPromise {
constructor(fn) {/** ... */}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
/**
* onFulfilled 成功时的回调,外面传过来的
* onRejected 失败时的回调,外面传过来的
* 还拿刚才封装的ajax请求来说,我们把服务器数据拿过来之后,就可以做我们想做的事了
* getData().then(
* (value)=>{
* // 数据拿到了,做我们想做的事
* this.xxxData = value
* },
* (reason)=>{
* // 失败的回调,这时候我们一般会弄个弹窗,提示一下用户出了什么错
* // 比如,网络超时、断网、未授权、等等等等
* }
* )
*
*
*/
then(onFulfilled, onRejected) {
// 调用完then方法,返回值仍然是一个promise,所以我们再new一个promise2,用来返回,这样就达到了链式调用的效果
// .then().then().then().then().then().... 想多少都行,无穷无尽的链式操作
const promise2 = new MyPromise((resolve, reject) => {
switch (this.status) {
// 状态为 FULFILLED 时,调用一下成功的回调
case FULFILLED:
onFulfilled()
break;
// 状态为 REJECTED 时,调用一下失败的回调
case REJECTED:
onRejected()
break;
// 状态为 PENDING 时,怎么办?????下一步再我们再研究
case PENDING:
// ...下一步再再研究
break;
}
})
return promise2
}
}
第五步:分析promise
的两种调用方式
(分析)众所周知,我们在使用promise
时,一般会有两种调用方式
方式一:直接在刚声明的
promise
对象链式调用then
方法,这种链式调用,当结果被resolve
后,会自动触发then
方法的回调,所以会把resolve
的结果打印出来(reject同理),比如jsconst test = new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve(111) }, 1000) // 直接链式调用,刚声明出来就调用,如果状态是fulfilled或者rejected时,会自动触发对应的回调,然后就可以在回调函数里操作我们其他的业务逻辑 }).then((value)=>{ console.log(value) console.log(test) })
方式二:同步调用
then
方法,先声明,然后同步调用,同步调用的时候,resolve的结果由于延迟了一秒才会执行,所以在同步执行then
方法的时候,所有的状态都是pending,这时候就需要考虑两件事了,(1)调用then
方法时,是pending状态怎么处理?(2)如何监听到状态的改变,然后去触发then
方法对应的回调函数?jsconst test = new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve(111) }, 1000) // 直接链式调用,刚声明出来就调用 }) test.then((value)=>{console.log(value)}) test.then((value)=>{console.log(value)}) test.then((value)=>{console.log(value)}) test.then((value)=>{console.log(value)}) console.log(test)
第六步:改写then
方法,使用getter
和setter
监听状态的改变
所以针对第四步的问题,和第五步分析中考虑的两个问题,来继续改写我们的代码,
对于第五步的第一个问题,(1)调用
then
方法时,是pending状态怎么处理?可以声明两个数组,将未执行的回调先存起来,等到状态改变时,再挨个从数组拿出来执行一遍。
对于第五步的第二个问题,(2)如何监听到状态的改变,然后去触发
then
方法对应的回调函数?可以使用es6中的getter和setter来监听constructor中属性值变化,显示的声明出getter和setter方法,然后就可以从缓存回调的数组中取出回调函数,挨个执行一遍,这正好应对了步骤五中的方式二,当我们同步的调用
then
方法时,同一个promise同步调用多次then
,每次调用时,状态都是pending,这时就将回调函数存缓存数组中,在状态发生改变时,在挨个执行,执行顺序与注册时的顺序正好是一致的
// 声明状态常量
/** ... */
class MyPromise {
// 声明两个私有数组,缓存状态未执行的回调函数
FULFILLED_CALLBACK_LIST = []
REJECTED_CALLBACK_LIST = []
/**
* 定义私有变量 _status,其对应的属性是构造器中的status
* 在使用getter、setter监听status时,我们操控 _status
* 防止死循环
* (
* 如果不用一个私有变量存储,那么在每次 对 status进行赋值和取值的操作时,
* 都会调用 setter和getter,这样就又是一波 赋值取值操作,然后就继续调用 setter和getter,
* 如此循环往复,成了死循环。
*
* 但是,当使用 _status 私有变量成员时,_status 并不会被getter、setter监听,所以就避免了死循环
*
* )
*/
_status = PENDING
constructor(fn) {/** ... */}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
/**
* 监听状态的改变,如果变成了fulfilled或者rejected,
* 就从缓存回调事件的数组中,把该执行的回调挨着执行一遍
*/
set status(newStatus) {
this._status = newStatus
switch (newStatus) {
case FULFILLED:
this.FULFILLED_CALLBACK_LIST.forEach(callback => {
callback(this.value)
});
break;
case REJECTED:
this.REJECTED_CALLBACK_LIST.forEach(callback => {
callback(this.reason)
});
break;
}
}
//
get status() {
return this._status
}
then(onFulfilled, onRejected) {
const promise2 = new MyPromise((resolve, reject) => {
switch (this.status) {
/** .... */
case PENDING:
// 状态为 PENDING 时,我们就先把回调存起来,等状态改变之后再拿出来执行
this.FULFILLED_CALLBACK_LIST.push(onFulfilled)
this.REJECTED_CALLBACK_LIST.push(onRejected)
break;
}
})
return promise2
}
}
第七步:then
方法参数校验,queueMicrotask
包裹回调方法,转换到微任务队列
接下来继续完善then
方法,先完善三个地方
- (1)
then(onFulfilled, onRejected)
,then
方法中的onFulfilled
和onRejected
是从外部接收的,需要校验参数,期望是函数 - (2)
onFulfilled, onRejected
,函数的执行必需在微任务中,这里简单的使用queueMicrotask
函数包裹一下 - (3)
onFulfilled, onRejected
,函数的执行过程中,可能会报错,毕竟我们也不知道外部传过来的函数里面都会执行哪些业务,会报错很正常,所以需要使用try...catch...
包裹一下,假如有报错,直接把错误reject(e)
出去
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
then(onFulfilled, onRejected) {
/**
* 校验 onFulfilled, onRejected
* 如果是函数,就还用自己
* 如果不是函数,我们就忽略,给个默认的函数
*/
const realOnFulfilled = this.isFunction(onFulfilled) ? onFulfilled : (value) => {
// 给的默认函数需要有返回值,为什么需要有呢?在下面的步骤会给出分析
return value
}
const realOnRejected = this.isFunction(onRejected) ? onRejected : (reason) => {
throw reason
}
const promise2 = new MyPromise((resolve, reject) => {
// 使用queueMicrotask改造成微任务
const fulfilledMicrotask = () => {
queueMicrotask(() => {
// 使用catch包裹,一旦出错就reject出去
try {
realOnFulfilled(this.value)
} catch (e) {
reject(e)
}
})
}
// 使用queueMicrotask改造成微任务
const rejectedMicrotask = () => {
queueMicrotask(() => {
// 使用catch包裹,一旦出错就reject出去
try {
realOnRejected(this.reason)
} catch (e) {
reject(e)
}
})
}
switch (this.status) {
case FULFILLED:
// 此处改为调用对应的微任务
fulfilledMicrotask()
break;
case REJECTED:
// 此处改为调用对应的微任务
rejectedMicrotask()
break;
case PENDING:
// 此处缓存数组改为存微任务
this.FULFILLED_CALLBACK_LIST.push(fulfilledMicrotask)
this.REJECTED_CALLBACK_LIST.push(rejectedMicrotask)
break;
}
})
return promise2
}
// 判断参数是否为函数
isFunction(param) {
return typeof param === 'function'
}
}
第八步:分析then(onFulfilled, onRejected)
方法中回调方法的返回值,抛出疑问,声明resolvePromise(promise2, x, resolve, reject)
方法
我们继续分析,除了第七步考虑的三点外,还有一个很重要的点需要考虑,在调用then(onFulfilled, onRejected)
方法时,外部传来的函数onFulfilled, onRejected
,很可能会有返回值,并且返回值会有很多种类型,先来抛出几点疑惑,
- (1)如果返回值仍然是一个
promise
怎么处理? - (2)如果返回值是一个对象或者函数怎么处理?
- (3)返回值就不是
promise
,也不是对象或者函数,那么就只能是基本类型了,这时候又怎么处理?
在这步我们先不管怎么处理,我们先把问题抛出来,写个处理的方法去处理,在第九步在专心考虑怎么处理这几种情况,所以接着第七步代码改写
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
then(onFulfilled, onRejected) {
/** ... */
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
// 返回值我们定义为 x
const x = realOnFulfilled(this.value)
/**
* 处理返回值的方法,我们定义为 resolvePromise
*
* 为什么参数会多传递个 当前的返回值promise2呢?
* 这是因为当返回值 x 也是一个Promise时,
* 把自己.then返回的promise2也传递过去,为了校验 x === promise2的情况,
* 如果它俩完全是同一个promise,会导致死循环
* */
this.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
// 返回值我们定义为 x
const x = realOnRejected(this.reason)
// 处理返回结果
this.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
/** ... */
})
return promise2
}
// 处理回调函数中的返回值,会有大量的if操作,因为有很多种情况,都应该考虑到
resolvePromise(promise2, x, resolve, reject) {
}
isFunction(param) {/** ... */}
}
第九步:剖析resolvePromise(promise2, x, resolve, reject)
方法
接着第八步抛出的疑惑,开始完善resolvePromise(promise2, x, resolve, reject)
方法的判断逻辑框架,由外到里,一点点剖析,分析得知,外部有四个if
判断
(1)如果
promise2 === x
,即,调用.then()
方法得到的promise
与执行完回调函数的返回值x
是同一个promise
,这显然是不合常理的,因为遵循Promise A+
规范的链式操作.then().then().then().then()...
,每次.then()
返回的结果都是一个新的promise
对象,如果调用完某一个回调后,比如jslet test = new Promise((resolve, reject)=>{ resolve(111) }).then( ()=>{ // 假设执行完这个回调后,由于一些意外的操作,返回值 x 与 执行完.then()后的返回值 是同一个promise // return promise2 } )
这样的话会导致死循环,因为如果相等的话就会按照返回值是
promise
来处理,会继续执行它的then()
方法来递归解析返回值,但是每次返回的x
都是这个promise
,因而就导致了死循环,所以在最初阶段就需要判断一下,如果x === promise2
,那么直接抛出类型异常。(2)如果
x instanceof MyPromise
,即,执行完回调的返回值x
仍然是一个promise
,比如下面这个例子,可以看出来,.then()
返回的promise
继承了x
的状态和结果,具体怎么实现,待会再看jslet test = new Promise((resolve, reject)=>{ resolve(111) }).then( (value)=>{ console.log(value) // 假设执行完这个回调后,又返回了一个Promise // return new Promise((resolve, reject)=>{ resolve(222) }) } )
(3)如果回调的返回值
x
是一个对象或者是方法,类似于下面这个例子jslet test = new Promise((resolve, reject)=>{ resolve(111) }).then( (value)=>{ console.log(value) // 假设执行完这个回调后,又返回了一个对象 // return { a: 'aaa', b: 'bbb' } } ) test.then(console.log)
jslet test = new Promise((resolve, reject)=>{ resolve(111) }).then( (value)=>{ console.log(value) // 假设执行完这个回调后,又返回了一个对象 // return { a: 'aaa', b: 'bbb', then: 'ccc' } } ) test.then(console.log)
jslet test = new Promise((resolve, reject)=>{ resolve(111) }).then( (value)=>{ console.log(value) // 假设执行完这个回调后,又返回了一个对象 // return { a: 'aaa', b: 'bbb', then: (resolve, reject)=>{ resolve(2222) } } } ) test.then(console.log)
jslet test = new Promise((resolve, reject)=>{ resolve(111) }).then( (value)=>{ console.log(value) // 假设执行完这个回调后,又返回了一个方法 // return ()=>{ console.log(2222) } } )
test.then(console.log)
运行完这几个例子,会发现,如果是对象,会找对象里有没有一个属性是`then`,看看符不符合`promise A+`中的then()方法的规范,即,`then((resolve, reject)=>{resolve(1111)})`,类似这样的方法,可以传成功回调和失败回调,如果有就会继承,没有的话就返回原结果`x`。 这里有点绕,待会直接看代码理解一下,记得把例子敲一下试试。 - (4)排除以上条件,剩下的只能是基本类型,如果是基本数据类型,直接返回结果
第十步:实现resolvePromise(promise2, x, resolve, reject)
方法,解析回调函数返回的结果x
第九步已经将所有的条件都分析了一遍,现在开始写第八步解析回调结果x
的方法resolvePromise(promise2, x, resolve, reject)
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
then(onFulfilled, onRejected) {
/** ... */
const promise2 = new MyPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
try {
// 返回值我们定义为 x
const x = realOnFulfilled(this.value)
this.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
try {
// 返回值我们定义为 x
const x = realOnRejected(this.reason)
// 处理返回结果
this.resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
/** ... */
})
return promise2
}
// 处理回调函数中的返回值,会有大量的if操作,因为有很多种情况,都应该考虑到
resolvePromise(promise2, x, resolve, reject) {
// 1、如果 .then()返回的promise 和 执行完回调返回值是同一个promise
if (x === promise2) {
// 不能相等,如果相等会死循环,应保证每次执行完返回的promise的唯一性
return reject(new TypeError('The promise and the return value are the same'))
}
// 2、如果返回值x是promise
if (x instanceof MyPromise) {
// 微任务中执行,继续解析Promise
queueMicrotask(() => {
x.then(
(y) => {
// 递归解析,一直解析成其他情况为止
this.resolvePromise(promise2, y, resolve, reject)
},
reject,
)
})
} else if (typeof x === 'object' || this.isFunction(x)) { // 3、如果返回值是对象或者函数
// 先判断是不是null
if (x === null) {
return resolve(x) // 直接返回结果
}
// 在看看对象里有没有符合promise规范的then方法
let then = null
try {
// 把这个then 取出来,万一报错了,直接reject出去
then = x.then
} catch (e) {
reject(e)
}
if (this.isFunction(then)) {
// 成功和失败的回调只能调用一个
let called = false
// 执行方法的时候包裹一个try..catch... 捕获方法里的异常
try {
// 如果是x.then是function
then.call(
x,
(y) => {
if (called) return
called = true
// 继续解析返回的结果
this.resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) return
called = true
// 直接reject回去
reject(r)
}
)
} catch (e) {
if (called) return
called = true
// 直接reject回去
reject(r)
}
} else { // x是对象,且x.then不是function || x直接就是一个function
resolve(x) // 直接resolve出去
}
} else { // 4、如果什么都不是,那只能是基本类型了,直接把结果resolve出去
resolve(x)
}
}
isFunction(param) {/** ... */}
}
第十一步:实现.catch()
方法
promise
除了可以使用.then(onFulfilled, onRejected)
方法的第二个参数获取失败的回调结果外,还可以使用.catch()
方法获得,比如
const test = new Promise((resolve, reject)=>{
setTimeout(()=>{
reject(111)
}, 1000)
}).then(
value=>{},
reason=>{
console.log(reason, 'onRejected')
}
)
// 或者
test.catch(reason=>{
console.log(reason, 'onRejected')
})
我们自己也实现一个
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
// 直接调用then方法,不传成功的回调,只传失败的回调即可
catch(onRejected) {
return this.then(null, onRejected)
}
then(onFulfilled, onRejected) {/** ... */}
resolvePromise(promise2, x, resolve, reject) {/** ... */}
isFunction(param) {/** ... */}
}
通过上面的十步,我们已经可以使用自己定义的promise,自己可以代入用例试一试,比如
const test = new MyPromise((resolve, reject)=>{
setTimeout(() => {
resolve(111)
}, 1000)
}).then(console.log)
console.log(test)
或者
const test = new MyPromise((resolve, reject)=>{
setTimeout(() => {
resolve(111)
}, 1000)
})
test.then(console.log)
test.then(console.log)
test.then(console.log)
test.then(console.log)
第十二步:实现类的静态方法Promise.resolve()
在实际的工作中,我们有时还会直接使用promise
的静态方法Promise.resolve(value)
直接返回一个promise
对象,存在两种情况
(1)如果
value
是一个基本类型,以这个基本类型为value
,返回一个promise
对象jsconst test = Promise.resolve(1111) test.then(console.log)
(2)如果
value
是一个promise
对象,那直接返回这个promise
jsconst test = Promise.resolve( new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve(111) }, 1000) }) ) test.then(console.log)
我们自己也实现一个MyPromise.resolve()
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
/**
* 注意:
* 静态成员方法只能通过类名调用
* 并且在方法体里也只能调用静态方法,没有this
*/
static resolve(value) {
// 如果value是一个Promise,直接返回
if (value instanceof MyPromise) {
return value
}
// 如果不是就直接以value为值,返回一个新的Promise
return new MyPromise((resolve) => {
resolve(value)
})
}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
then(onFulfilled, onRejected) {/** ... */}
resolvePromise(promise2, x, resolve, reject) {/** ... */}
isFunction(param) {/** ... */}
}
第十三步:实现类的静态方法Promise.reject()
Promise.reject(reason)
直接返回一个fulfilled
状态的promise
,比如
const test = Promise.reject(1111)
test.then(
(value)=>{console.log(value,'fulfilled')},
(reason)=>{console.log(reason,'rejected')},
)
自己也实现一个
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
static resolve(value) {/** ... */}
static reject(reason) {
// 以reason为值,返回一个新的Promise
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
then(onFulfilled, onRejected) {/** ... */}
resolvePromise(promise2, x, resolve, reject) {/** ... */}
isFunction(param) {/** ... */}
}
第十四步:实现类的静态方法Promise.race()
promise
的race
方法,是传入一个数组(其实是类数组,只要可以被迭代都可以),数组里存了一堆等待执行的promise
对象,如果不是promise
,内部直接给全部转换为promise
,我们并不知道这些``promie最终执行的结果,当使用
Promise.race(arr).then()时,这些
promise一旦有一个被
resolve了,就直接将结果返回,执行时是同步的,可以使用
for`循环来模拟,for循环默认为同步执行
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
static resolve(value) {/** ... */}
static reject(reason) {/** ... */}
static race(iterableList) {
return new MyPromise((resolve, reject) => {
/**
* 判断传入的参数是否可迭代
* */
if (!MyPromise.isIterable(iterableList)) {
return reject(new TypeError(`${iterableList} is not iterable (cannot read property Symbol(Symbol.iterator))`))
}
// 2、将类数组转换为数组
const promiseList = Array.from(iterableList)
const promiseLength = promiseList.length
// 如果是一个空数组,直接resolve一个空数组
if (promiseLength === 0) {
return resolve([])
} else {
// 3、同步执行数组中的Promise
for (let i = 0; i < promiseLength; i++) {
// 4、为了防止某一个参数不是Promise,直接全转换一下
MyPromise.resolve(promiseList[i]).then(
(value) => {
// 5、一旦有结果了,直接返回
return resolve(value)
},
(reason) => {
// 5、
return reject(reason)
}
)
}
}
})
}
/**
* @description 判断value是否可迭代
* @param {*} value
* @returns {Boolean} true:可迭代;false:不可迭代
*/
static isIterable(value) {
// 如果是空或undefined 直接返回false
if (value === null || value === undefined) {
return false
} else {
// 对象里如果没有Symbol.iterator,默认是不可迭代的
// 可迭代的对象都会默认实现Symbol.iterator迭代器
return !(value[Symbol.iterator] === undefined)
}
}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
then(onFulfilled, onRejected) {/** ... */}
resolvePromise(promise2, x, resolve, reject) {/** ... */}
isFunction(param) {/** ... */}
}
第十五步:实现类的静态方法Promise.all()
promise
的all
方法与race
方法的不同点是all
方法只有所有结果都resolve
时才会返回结果,一旦有一个reject
就会走失败的回调
// 声明状态常量
/** ... */
class MyPromise {
/** ... */
constructor(fn) {/** ... */}
static resolve(value) {/** ... */}
static reject(reason) {/** ... */}
static race(iterableList) {/** ... */}
static all(iterableList) {
return new MyPromise((resolve, reject) => {
// 1、判断参数是否是可迭代的
if (!MyPromise.isIterable(iterableList)) {
return reject(new TypeError(`${iterableList} is not iterable (cannot read property Symbol(Symbol.iterator))`))
}
// 2、转换为数组
const promiseList = Array.from(iterableList)
const promiseLength = promiseList.length
/**
* 取一个计数器,每次执行resolve回调时就+1
* 并声明一个返回值的数组,每次resolve时就将返回值存进去,
* 只有当计数器的长度和promise个数相等时,即所有promise都成功执行了,
* 然后就将存成功结果的数组resolve出去
*/
let resolvedCount = 0
let resolvedValues = new Array(promiseLength)
// 如果是空数组,直接返回空数组
if (promiseLength === 0) {
return resolve([])
} else {
// 3、同步执行
for (let i = 0; i < promiseLength; i++) {
// 4、全部转换为promise对象
MyPromise.resolve(promiseList[i]).then(
(value) => {
// 成功时就+1
resolvedCount++
// 5、存入对应的value到结果数组中
resolvedValues[i] = value
if (resolvedCount === promiseLength) {
return resolve(resolvedValues)
}
},
(reason) => {
// 6、一旦有失败的,就将失败结果reject出去
return reject(reason)
},
)
}
}
})
}
static isIterable(value) {/** ... */}
resolve(value) {/** ... */}
reject(reason) {/** ... */}
get status() {/** ... */}
set status(newStatus) {/** ... */}
then(onFulfilled, onRejected) {/** ... */}
resolvePromise(promise2, x, resolve, reject) {/** ... */}
isFunction(param) {/** ... */}
}
总结
以上就自己实现了一个自己的promise,可以试着敲一敲,代入一些用例试一试,总共实现了promise的以下内容
new Promise()
时都做了哪些事then
方法内部的执行机制- promise链式执行的原理
.catch
方法执行原理- 手写
promise.race()
- 手写
promise.all()
源代码地址:https://gitee.com/hrbust_cheny/note_code/blob/master/ES6/promise/test9.js