Javascript:手撕Promise

JavaScript:手撕Promise源码

一、什么是Promise?

  1. Promise可以实现异步操作;
  2. 可以将异步操作队列化,按照期望的顺序执行任务;
  3. 拥有三种状态:等待(Pending)、已兑现(Fullfilled)、已拒绝(Rejected),且状态只能从Pending向其他两个转化,一旦变化就冻结了;
  4. 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

二、Promise的使用过程

  • 一个简单的promise使用如下:
const p = new Promise((resolve,reject)=>{
    try{
        setTimeout(()=>{
        	resolve('I am a Promise');
    	},2000)
    }catch(err){
        reject(err);
    }
})

p.then(res=>{
    console.log(res);	// I am a Promise
})
  • 首先,我们需要先构造一个promise对象,传入执行resolvereject的函数(executor);
  • 然后,通过调用promise对象的then,来获取执行executor函数后返回的结果res;
  • 最后执行then里面的回调,打印出结果。

三、Promise的执行流程

  1. 首先,在构造过程中,Promise会初始化自己的状态为Pending,调用executor函数,把对象自身的resolve方法和reject方法传入executor中,执行resolve方法(如果这里有异步操作,将会把resolve加入微任务队列,继续顺下执行then方法);

  2. 执行then方法,检测对象状态,若为Pending,则将参数存入上一个状态的任务队列中;否则,根据状态来执行onFullfilled或者onRejected

  • 整个过程下来,任务队列是非常重要的思想;

四、开始手撕

1. 构造函数

  • Promise的构造函数需要完成:
    1. 初始化状态;
    2. 初始化任务队列;
    3. 执行回调函数executor;
const PENDING = 'pending';
const FULLFILLED = 'fullfilled';
const REJECTED = 'rejected';

class MyPromise{
    constructor(executor){
        this.status = PENDING;
        this.onFullFilledCallbacks = [];
        this.onRejectedCallbacks = [];
        this.value = '';
        this.reason = '';
        // 这里的_resolve和_reject方法是Promise内部处理的方法,后续补充
        try{
        	executor(this._resolve.bind(this),this._reject.bind(this));	
        }catch(err){
            this._reject(err);
        }
    }
}

2. Resolve方法

  • Promise的resolve方法是把Promise状态变更为已兑现时的处理数据方法;
  • 当构造函数调用回调函数时,将调用此方法;
class MyPromise{
    ......
    _resolve(value){
        if(this.status === PENDING){
            this.status = FULLFILLED;
            this.value = value;
            // 执行任务队列里的函数
            this.onFullFilledCallbacks.forEach(fn=>fn());
        }
    }
}

3. Reject方法

  • 当某个操作出现异常时,Promise将会执行reject方法;

  • 该方法是把Promise状态变更为已拒绝时的数据处理方法;

  • 当构造函数调用回调方法时,将调用此方法;

class MyPromise{
    ......
    _reject(reason){
        if(this.status === PENDING){
            this.status = REJECTED;
            this.reason = reason;
            // 执行任务队列的函数
            this.onRejectedCallbacks.forEach(fn=>fn());
        }
    }
}

4. then方法(无链式调用)

  • Promise的then方法用于接受前一个resolve或者reject时返回的结果;
  • 这里先简易实现一个无链式调用的then方法;
class MyPromise{
    ......
    then(onFullfilled,onRejected){
        if(this.status === FULLFILLED){
            // 这里的setTimeout用于模拟微任务,实现异步
            setTimeout(()=>{
            	// 将resolve的执行结果传入
                onFullfilled(this.value);
            })
        }
        
        if(this.status === REJECTED){
            setTimeout(()=>{
                // 将reject的执行结果传入
                onRejected(this.reason);
            })
        }
        if(this.status === PENDING){
            // 若还未执行resolve或者reject方法,说明Promise对象的回调操作是异步的。则状态还是等待
            this.onFullFilledCallbacks.push(()=>{
                setTimeout(()=>{
                    onFullfilled(this.value);
                })
            });
        	this.onRejectedCallbacks.push(()=>{
                setTimeout(()=>{
                    onRejected(this.value);
                })
            })
            // 这一步导致无法实现链式调用,每次then的结果都是同一个
            return this;
        }
    }
}
  • 来看看测试案例:
const p = new MyPromise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('hello1');
    })
})

p.then(res=>{
    console.log(res);	
    return 'hello2';
})
.then(res=>{
    console.log(res);
})

**********************控制台结果***************************
[2 seconds later]
hello1
hello1
  • 从结果可以看出,无论调用几次then,返回结果总是第一个res;
  • 但我们已经整体实现了Promise的基本思路,接下来就是进行链式功能的改造;

5. then方法(链式调用)

  • 链式调用是promise实现异步操作队列化的关键,它能够阻塞进程,使程序如上下文顺序一般执行;
  • 上一小点实现了无链式的then方法,无论执行几次都是第一个的res,无法接收到then返回的值;
  • 为了接收这个值,我们对then方法做一点小改动,使之每次返回一个新的Promise对象;
class MyPromise{
    ......
    then(onFullfilled,onRejected){
        const promise2 = new MyPromise((resolve,reject)=>{
            // 这里的this指向的是调用then方法的Promise对象
            // 这里的this指向的是调用then方法的Promise对象
            // 这里的this指向的是调用then方法的Promise对象
            // 这里的this指向的是调用then方法的Promise对象
            // 这里的this指向的是调用then方法的Promise对象
            
            // 所以以下操作都是在原来的Promise上执行
            if(this.status === FULLFILLED){
            	setTimeout(()=>{
                	try{
                        let x = onFullfilled(this.value);
                        resolve(x);
                    }catch(err){
                        reject(err);
                    }
            	})
        	}
        
        	if(this.status === REJECTED){
            	setTimeout(()=>{             	
                    try{
                        let x = onRejected(this.reason);
                        resolve(x);
                    }catch(err){
                        reject(err);
                    }
            	})
        	}
        	if(this.status === PENDING){
            	// 若还未执行resolve或者reject方法,说明Promise对象的回调操作是异步的。则状态还是等待
            	this.onFullFilledCallbacks.push(()=>{
                	setTimeout(()=>{
                    	try{
                        	let x = onFullfilled(this.value);
                        	resolve(x);
                    	}catch(err){
                        	reject(err);
                    	}
                	})
            	});
        		this.onRejectedCallbacks.push(()=>{
                    setTimeout(()=>{
                    	try {
              				let x = onRejected(this.reason);
              				resolve(x);
            			} catch (error) {
              				reject(error);
            			}
                	})
            	})
        	}
        })
        return promise2;
    }
}
  • 看看测试案例:
let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("Avril");
  }, 2000);
});


let p2 = p1.then((res) => {
  console.log(res);
  return "Lavigne";
});

let p3 = p2.then((res) => {
  console.log(res);
  return "Tomorrow";
});

***********************控制台结果******************************
    
[2 seconds later]
Avril
Lavigne
Tomorrow
  • 每次调用then方法后,返回一个新的Promise对象,这样能够接收到上一个then返回的数据;
  • 如果当前状态为Pending,then方法就会将回调存入上一个promise的任务队列,等上一个promise执行resolve方法后,才执行这个then的回调函数;
  • 这样一来,我们就成功实现了链式调用,但是有个缺点,当我们的then返回了新的promise对象时,程序就会抛异常。

6. then的健壮性强化:isThenable

  • 上文说到then方法的缺点,接下来我们就对其进行修改;
  • 上一小点的then函数中,我们只用一个变量将onFullfilled的返回值接收后,直接传入下一个Promise的resolve中,未对其进行检测;
  • 因此,我们可以封装一个检测函数,检测then返回值是否是Thenable的。
function isThenable(obj){
    return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}
  • isThenable函数用于判断obj是否为object或者function,然后判断obj是否有then这个方法。
function resolvePromise(x,resolve,reject){
    if(isThenable(x)){
        try{
            x.then(resolve,reject);
        }catch(err){
            reject(err);
        }
    }else{
        resolve(x);
    }
}
  • resolvePromise函数用于根据x对象的类型来进行处理:
    1. 若为Thenable对象, 将执行他的then方法,将resolve和reject方法作为参数传入;
    2. 否则,将直接执行resolve方法,将x作为result;

  • 有了健壮性操作,我们再对MyPromise的部分方法进行修改:
class MyPromise{
    ......
    _resolve(value){
        if(isThenable(value)){
            return value.then(this._resolve.bind(this),this._reject.bind(this));
        }
        if(this.status === PENDING){
            this.status = FULLFILLED;
            this.value = value;
            this.onFullFilledCallbacks.forEach((fn)=>fn());
        }
    }
	then(onFullfilled, onRejected) {
    	// 默认值
    	onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : value=>value;
    	onRejected = typeof onRejected === 'function' ? onRejected : reason=>{
        	console.log('出错了');
        	throw new Error(reason);
    	};
 
    	const p2 = new MyPromise((resolve, reject) => {
      	if (this.status === FULLFILLED) {
        	setTimeout(() => {
          	try {
            	let x = onFullfilled(this.value);
            	resolvePromise(x,resolve,reject);
          	} catch (error) {
            	reject(error);
          	}
        	});
      	}

      	if (this.status === REJECTED) {
        	setTimeout(() => {
          	try {
            	let x = onRejected(this.reason);
            	resolvePromise(x,resolve,reject);

          	} catch (error) {
            	reject(error);
          	}
        });
      }

      	if (this.status === PENDING) {
        	this.onFullFilledCallbacks.push(() => {
            	// 这里this指的还是前一个promise的onFullfilledCallbacks
            	// 用闭包封装 ,记住了this的onFullfilled和this的resolve
          		setTimeout(() => {
            		try {
              			let x = onFullfilled(this.value);
              			resolvePromise(x,resolve,reject);	
            		} catch (error) {
              			reject(error);
            		}
          		});
        	});
        	this.onRejectedCallbacks.push(() => {
          		setTimeout(() => {
            		try {
              			let x = onRejected(this.reason);
              			resolvePromise(x,resolve,reject);
            		} catch (error) {
              			reject(error);
            		}
          		});
        	});
      	}
    });

    return p2;
  }
	
}
  • 这里then方法首先先给参数赋予默认值;
  • 与之前的相比,再处理新Promise的回调函数时,调用resolvePromise方法来处理then方法返回的值;

7. 一些静态方法的实现

  • Promise.resolve方法是resolve的一个语法糖,返回一个Promise对象:
class MyPromise{
    ......
    static resolve(param){
        if(param instanceof MyPromise){
            return param;
        }
        return new MyPromise((resolve,reject)=>{
            if(isThenable(param)){
                setTimeout(()=>{
                    param.then(resolve,reject);
                })
            }else{
                resolve(param);
            }
        })
    }
}
  • Promise.reject同上,也返回一个Promise对象:
class MyPromise{
	......
    static reject(reason){
        return new MyPromise((resolve,reject)=>{
            reject(reason);
        })
    }
}
  • Promise.all方法返回一个Promise实例,接收一个类型为Promise的数组,当数组中的所有Promise都实现时才返回实现状态;当有一个Promise执行失败了,则返回失败,而且返回的Promise的值就是第一个Promise的失败的原因;
class MyPromise{
    ......
    static all(promises){
        return new NyPromise((resolve,reject)=>{
            let index = 0;
            let result = [];
            if(promises.length === 0){
                resolve(result);
            }else{
                function processValue(i,data){
                    result[i] = data;
                    if(++index === promises.length){
                        resolve(result);
                    }
                }
                for(let i=0;i<promises.length;i++){
                    MyPromise.resolve(promises[i].then(data=>{
                        processValue(i,data);
                    },err=>{
                        reject(err);
                        return;
                    }))
                }
            }
        })
    }
}
  • Promise.race返回一个Promise实例,参数传入一个类型为Promise的数组,当参数中的某个Promise成功resolve或reject后,会返回这个Promise的结果:
class MyPromise{
    ......
    static race(promises){
        return new MyPromise((resolve,reject)=>{
            if(promises.length === 0){
                return;
            }else{
                for(let i=0;i<promises.length;i++){
                    MyPromise.resolve(promises[i].then(data=>{
                      	resolve(data);
                        return;
                    },err=>{
                        reject(err);
                        return;
                    }))
                }
            }
        })
    }
}

8. catch和finally方法

  • catch返回一个Promise实例,同时只注册一个失败的回调;
  • 当Promise的状态变为rejected时,就会执行这个catch方法;
class MyPromise{
    ......
    catch(onRejected){
        return this.then(null,onRejected);
    }
}
  • finally方法返回一个Promise实例,当所有then方法执行完后
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值