手写Promise
根据Promises/A+规范实现Promise
定义初始结构
Promise的使用是通过 new来实例化一个Promise对象,并且传入一个函数
const P = new Promise((resolve:any,reject:any) => {
})
所以我们需要先定义Promise类,并在类中定义一个构造函数,在构造函数中执行传入的函数,并且执行的函数还有两个参数resolve和reject
export class MyPromise {
constructor(executor:Function){
executor(resolve,reject)
}
}
实现Promise状态的改变
Promise的状态分为 pending(等待),fulfiiled(成功),rejected(失败),所以我们也要先把这三个状态准备一下,这边可以定义3个常量方便后续的维护
protected static PENDING = 'pending'
protected static FULFILLED = 'fulfilled'
protected static REJECTED = 'rejected'
实现resolve,和reject来实现状态的改变
我们需要准备好变量promiseState和promiseResult
promiseState来保存Promise的状态改变,并在构造函数中给他初始化,初始化状态为pending` promiseResult来保存resolve和reject执行是传入的参数,并在构造函数中给他初始化,初始化值为null
constructor(executor:Function){
this.promiseState = PromiseBase.PENDING
this.promiseResult = null
executor(resolve,reject)
}
public promiseState:string
public promiseResult:unknown
实现resolve和reject函数,来改变状态
由于promise的状态只改变一次,所以加上对应的判断,只有在pending状态下才进行改变
public resolve(result:unknown): void {
if(this.promiseState === MyPromise.PENDING){
this.promiseState = MyPromise.FULFILLED
this.promiseResult = result
}
}
public reject(reason:unknown): void {
if(this.promiseState === MyPromise.PENDING){
this.promiseState = MyPromise.REJECTED
this.promiseResult = reason
}
}
有了resolve和reject函数,构造函数中的执行器参数就可以调用这两个函数来进行初始化
constructor(executor:Function){
this.promiseState = PromiseBase.PENDING
this.promiseResult = null
// 执行器为外部传入函数,在实例化回调函数的this指向不指向类本身,需要用bind改变this指向
executor(this.resolve.bind(this),this.reject.bind(this))
}
实现then方法
Promise的then方法接收2个回调函数,onFulfilled和onRejected,并且返回一个新的Promise
onFulfilled会在状态为fulfiied时执行,onRejected会在状态为rejected时执行
const P = new Promise((resolve:any,reject:any) => {
resolve('1')
})
p.then(
// onFulfilled
(result:any) => {
// onFulfilled回调会携带resolve执行的参数
console.log(result) //输出'1'
},
// onRejected
(reason:any) => {
//初始化时调用的是resolve函数,所以状态为fulfiiled,所以onRejected不会执行
console.log(reason)
}
)
const P1 = new Promise((resolve:any,reject:any) => {
reject(new Error ('1'))
})
p.then(
// onFulfilled
(result:any) => {
//初始化时调用的是reject函数,所以状态为rejected,所以onFulfilled不会执行
console.log(result)
},
// onRejected
(reason:any) => {
// onRejected回调会携带reject执行的参数
console.log(reason) // 输出 Eroor 1
}
)
综上可实现我们的then方法
public then(onFulfilled?:any,onRejected?:any){
const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
if(this.promiseState === MyPromise.FULFILLED){
onFulfilled(this.promiseResult)
}
if(this.promiseState === MyPromise.REJECTED){
onRejected(this.promiseResult)
}
}
}
异常校验和错误捕获
我们知道Promise是无法被try/catch的
try{
const P = new Promise((resolve:any,reject:any) => {
new Error('错啦错啦')
})
}catch(e){
// 无法捕获
console.log(e)
}
那是因为在内部构造函数中已经把错误捕获了,如果发生异常则直接执行reject,所以我们来改造一下构造函数
constructor(executor:Function){
this.promiseState = PromiseBase.PENDING
this.promiseResult = null
try{
// 执行器为外部传入函数,在实例化回调函数的this指向不指向类本身,需要用bind改变this指向
executor(this.resolve.bind(this),this.reject.bind(this))
}catch(error){
// 发生错误时捕获错误
// 这边不用bind来改变this指向是因为这边是立即执行而不是实例化后再执行,所以this指向不会发生改变
this.reject(error)
}
}
同时Promise.then接收到的2个参数并不一定是一个函数
const P = new Promise((resolve:any,reject:any) => {
reject('1')
})
p.then(
undefined,
(reason:any) => {
console.log(reason) // 输出'1'
}
)
所以我们的then方法要加上参数校验,不是函数直接返回
public then(onFulfilled?:any,onRejected?:any){
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason;
};
const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
if(this.promiseState === MyPromise.FULFILLED){
onFulfilled(this.promiseResult)
}
if(this.promiseState === MyPromise.REJECTED){
onRejected(this.promiseResult)
}
}
}
实现异步
Promise的then函数中的回调是异步执行的,并且会放进微队列中以微任务执行,这边常常会被出面试题,来考察事件循环的掌握程度
面试题: 说出以下执行顺序
console.log('1')
setTimeout(() => {
console.log('2')
},0)
const P = new Promise((resolve:any,reject:any) => {
resolve('3')
})
P.then(
(result:any) => {
console.log(result)
console.log('4')
setTimeout(() => {
console.log('6')
})
}
)
console.log('5')
// 输入顺序 1 5 3 4 2 6
实现then的回调函数异步执行
根据Promises/A+ 规范,回调函数可以放在宏队列也可以放在微队列,所以我们直接用SetTimeout将回调函数包一下,这边为了后续的拓展,封装了一个方法去将回调函数异步,另外再封装一个函数用来判断参数是否为函数
static taskMircoQueue(callback:any){
// TODO 暂时以setTimeout为准
// 后面补充node环境微任务nextTick及浏览器环境微任务MutationObserver
return setTimeout(callback,0)
}
static isFunc(func:any){
const result = typeof func === 'function' ? true : false
return result
}
改造后的then方法
public then(onFulfilled?:any,onRejected?:any){
const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
if(this.promiseState === MyPromise.FULFILLED){
taskMircoQueue(() => {
if(!isFunc(onFulfilled)){
resolve(this.promiseResult)
}else{
onFulfilled(this.promiseResult)
}
})
}
if(this.promiseState === MyPromise.REJECTED){
taskMircoQueue(() => {
if(!isFunc(onRejected)){
reject(this.promiseResult)
}else{
onRejected(this.promiseResult)
}
})
}
}
}
处理pending状态下的回调
then方法中如果又遇到异步执行后才改变状态时,我们就需要将回调函数先进行保存,后续再进行输出
// 根据事件循环的执行机制,渲染主线程会优先执行微队列中的任务,所以会先执行then,而Promise的状态此时还没有改变,所以此时的状态为pending
const P = new Promise((resolve:any,reject:any) => {
// setTimeout是宏任务
setTimeout(() => {
resolve('1')
},0)
})
// Promise.then是微任务
P.then((result:any) =>{
console.log(resolve)
})
所以我们需要在then方法中增加pending的逻辑判断,将pending时的回调存起来,然后在resolve和reject函数中去调用,由于Promise.then还具有链式调用,所以我们需要用数组来存储回调
/**
* 成功回调函数集合
*/
public onFulfilledCallBacks:Array<any>
/**
* 失败回调函数集合
*/
public onRejectCallBacks:Array<any>
改造后的resolve和reject
public resolve(result:unknown): void {
if(this.promiseState === MyPromise.PENDING){
this.promiseState = MyPromise.FULFILLED
this.promiseResult = result
this.onFulfilledCallBacks.forEach((callback:Function) => {
callback(result)
})
}
}
public reject(reason:unknown): void {
if(this.promiseState === MyPromise.PENDING){
this.promiseState = MyPromise.REJECTED
this.promiseResult = reason
this.onRejectCallBacks.forEach((callback:Function) => {
callback(reason)
})
}
}
改造后的then
public then(onFulfilled?:any,onRejected?:any){
const _promise = const _promise = new MyPromise((resolve:any,reject:any) => {
if(this.promiseState === MyPromise.PENDING){
this.onFulfilledCallBacks.push(() =>{
taskMircoQueue(() => {
if(!isFunc(onFulfilled)){
resolve(this.promiseResult)
}else{
onFulfilled(this.promiseResult)
}
})
})
this.onRejectCallBacks.push(() => {
taskMircoQueue(() => {
if(!isFunc(onRejected)){
reject(this.promiseResult)
}else{
onRejected(this.promiseResult)
}
})
})
}
if(this.promiseState === MyPromise.FULFILLED){
taskMircoQueue(() => {
if(!isFunc(onFulfilled)){
resolve(this.promiseResult)
}else{
onFulfilled(this.promiseResult)
}
})
}
if(this.promiseState === MyPromise.REJECTED){
taskMircoQueue(() => {
if(!isFunc(onRejected)){
reject(this.promiseResult)
}else{
onRejected(this.promiseResult)
}
})
}
}
}
then方法的链式调用
这边根据Promises/A+规范,链式调用需满足以下几点
- 1.then方法返回一个新的Promise
- 2.不论Promsie被reject或resolve,都会执行Promise解决过程
所以我们需要写一个Promise解决过程的函数resolvePromise,通过resolvePromise对resolve和reject进行增强
resolvePromise需要处理以下几种情况
- 1.promise与x指向对一对象,则拒绝执行(防止循环引用)
- 2.如果x的返回值为Prmose,则使新的promise接收x的状态
- 3.如果resolve和reject均被调用或者被同一参数调用多次,则优先采用首次调用并忽略剩下的调用
- 4.如果x是thenable对象,并且x.then是函数,则将x作为函数的作用域调用resolve或reject
- 5.如果x是thenable对象,并且x.then不是函数,以x作为参数执行Promise
/**
* Promises/A+规范 promise解决过程
* 针对resolve和reject的不同值进行处理
* @param promise then方法返回的新的promise
* @param x then方法中onFulfiied或onReject的返回值
* @param resolve 新promise的resolve方法
* @param reject 新promise的reject方法
*/
static resolvePromise(promise:any,x:any,resolve:any,reject:any){
// Promises/A+规范 如果promise和x指向同一对象,以typeError为据因拒绝执行promise
// 如果从onFulfilled或onReject中返回的x就是新promise,则会导致循环引用问题,这部分就是处理循环引用问题导致的报错
if(x === promise){
throw new TypeError("chaining cycle detected for promise")
}
if(x instanceof MyPromise){
// Promises/A+规范 如果x为promise,则使新的promise接受x的状态
x.then((_x:any) => {
this.resolvePromise(promise,_x,resolve,reject)
})
}
if(x!=null && ((typeof x === 'object' || (typeof x === 'function')))){
let then
try{
// 把x.then赋值为then
then = x.then
}catch(e){
// 如果x.then的值抛出异常错误e,则以e为拒因拒绝promise
return reject(e)
}
if(typeof then === 'function'){
// 如果resolve和reject均被调用或者被同一参数调用多次,则优先采用首次调用并忽略剩下的调用
let called:boolean = false
try{
then.call(
x,
// 如果resolve以值y为参数被调用,则运行[[Resolve]](promise,y)
(y:any) => {
if(called) return
called = true
this.resolvePromise(promise,y,resolve,reject)
},
// 如果reject以据因r为参数被调用,则以据因r拒绝promise
(r:any) => {
if(called) return
called = true
reject(r)
}
)
}catch(e){
if(called) return
called = true
reject(e)
}
}else{
// 如果then不为函数,以x为参数执行promise
resolve(x)
}
}else{
// 如果x不为函数或对象,以x为参数执行promise
return resolve(x)
}
}