Promise的出现解决了js异步操作只能用嵌套回调来实现的缺憾。由于ES6提供的Promise在IE中无法使用,所以我们有必要掌握Promise的实现方式,上篇文章已经分析过ES6中Promise的用法,感兴趣的可以移步这里。
本文主要基于Promises/A+来分析如何实现一个符合规范的Promise。
基本要求
先来看几个术语:
promise:表示是一个行为符合Promises/A+的,具有then方法的对象或者函数。
thenable:表示是一个具有then方法的对象或者函数。
value:任何合法的JavaScript的值,包括undefined、thenable或者promise。
exception:用then语句抛出来的值。
reason:表示拒绝Promise的原因。
下面来看要求:
Promise的状态必须是以下三者中的其中一个:pending、fulfilled、rejected。
当promise处于pending状态:可以被转换到fulfilled或者reject其中一个;
当promise处于fulfilled状态:不可过渡到其他状态,并且必须有一个value且该值不能被改变
当promise处于rejected状态:不可过渡到其他状态,并且必须有一个reason且该值不能被改变
promise必定有一个then方法,then方法的要求是什么呢?
promise.then接受两个参数:onFulfilled、onRejected,这两个参数都是可选的,并且如果这两者传入的类型不是函数的话,则必须忽略它。
onFulfilled:必须是函数,并且在promise的状态为fulfilled之后调用,且将value值作为第一个参数。在promise完成之前不可调用。且不可多次调用。
onRejected:必须是函数,并且在promise的状态为rejected之后调用,并且将reason值作为第一个参数。在promise rejected之前不可调用。且不可多次调用。
有的时候,我们会给同一个promise实例执行多次then方法,比如:
let promise1 = new Promise((resolve,reject)=>{
resolve('success')
})
promise1.then((value)=>{console.log(value,1)})
promise1.then((value)=>{console.log(value,2)})
那么相应的onFulfilled和onRejected回调必须按照其发起调用的顺序执行。
雏形
根据以上信息,我们可以写出一个简单的雏形:
//promise具有三种状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise{
constructor(executor){
this.status = PENDING;
this.value = null;//成功原因
this.reason = null;//失败原因
this.onFulfilledCallback = [];//成功的回调函数,因为要处理多次then方法的情况,所以是数组
this.onRejectedCallback = [];//失败的回调函数
let resolve =(value)=>{
//当状态为pending状态的时候才可以去改变状态,并且分别将value和reason赋值给对应值,并去执行相应回调函数
if(this.status == PENDING){
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallback.forEach(fn=>fn())
}
}
let reject =(value)=>{
if(this.status == PENDING){
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallback.forEach(fn=>fn())
}
}
try{
executor(resolve,reject)
}catch(err){
reject(err);//有报错会直接执行reject函数将状态变为失败rejected
}
}
then(onFulfilled,onRejected){
//当执行到then的时候,状态已经是fulfilled状态或者是rejected状态,那么就直接执行回调,并且将value/reason作为第一个参数
if(this.status == FULFILLED){
onFulfilled(this.value);
}
if(this.status == REJECTED){
onRejected(this.reason);
}
//当执行到then的时候,状态还是pending状态,那么需要将回调存起来,等到状态改变的时候再去执行
if(this.status == PENDING){
this.onFulfilledCallback.push(()=>{
//此处可以放一些自己的逻辑
onFulfilled(this.value);
})
this.onRejectedCallback.push(()=>{
//此处可以放一些自己的逻辑
onRejected(this.reason);
})
}
}
}
链式调用、值的穿透
我们再来继续看一下then的要求,首先then的返回值必须是一个promise。如果onFulfilled或者onRejected返回一个value x,那么这个x被promise2执行resolve返回,供下一个then使用。
如果onFulfilled或者onReject过程中发生了错误,那么这个错误e,被promise2执行reject作为reason返回。
由于onFulfilled和onRejected都是可选的参数,所以当未提供某个处理函数的时候,这个值要被传递到下一个then处理函数执行。
由以上信息,我们可以补充我们的then方法:
then(onFulfilled,onRejected){
//返回一个promise
let promise1 = new Promise((resolve,reject)){
//当未传递onFulFilled或者onRejected处理函数的时候,需要将值向下传递放到下一个then中
// 如何做到向下传递?相当于就是不做处理原样返回
onFulfilled = typeof onFulfilled == 'function'?onFulfilled:v=>v;
onRejected = typeof onRejected == 'function' ? onRejected: error => { throw error };
if(this.status == FULFILLED){
try{
// 此处用settimeout的原因是,下面的resolvePromise中用到了promise1,如果直接用,promise1会是undefined
setTimeout(()=>{
let x = onFullfilled(this.value);
//将函数的执行结果x 作为返回值传递给下一个then
//此处将处理逻辑提出去写是为了处理更加复杂的返回值,见下文。此处我们可以简单的理解为resolve(x)
resolvePromise(promise1,x,resolve,reject);
},0)
}catch(err){
//失败的时候,直接reject
reject(err);
}
}
if(this.status == REJECTED){
try{
setTimeout(()=>{
let x = onRejected(this.reason);
resolvePromise(promise1,x,resolve,reject);
},0)
}catch(err){
reject(err);
}
}
if(this.status == REJECTED){
this.onFulfilledCallback.push(()=>{
try{
setTimeout(()=>{
let x =onFulfilled(this.value);
resolvePromise(promise1,x,resolve,reject);
},0)
}catch(err){
reject(err);
}
})
this.onRejectedCallback.push(()=>{
try{
setTimeout(()=>{
let x = onRejected(this.reason);
resolvePromise(promise1,x,resolve,reject);
},0)
}catch(err){
reject(err);
}
})
}
}
return promise1;
}
处理各种情况下的resolve参数
- 如果x和promise是同一个,则需要返回一个错误,因为这会造成死循环
- 如果x不是一个对象或者函数,就用x来履行promise
- 如果x的值也是一个promise,则需要将x的最终状态作为最终状态
- 如果x的值是对象或者函数,将x.then作为then。如果then是函数,那么第一个参数是resolvePromise,第二个参数是rejectPromise,当使用y值调用resolvePromise的时候,运行resolve(y),当使用r值调用rejectPromise的时候,运行reject(r);如果多次调用,那么第一个优先调用,并且忽略其他调用。如果处理过程中出现错误,则抛出错误到下一个then中。
- 如果then不是一个函数,就用x来履行promise
根据以上要求,我们可以写出一个更加严谨的处理resolve的参数的函数,也就是上文我们留下疑问的resolvePromise。
function resolvePromise(promise,x,resolve,reject){
// 如果x和promise是同一个,那么需要返回一个错误,因为这可能会造成死循环
if(promise ==== x){
return reject(new TypeError('cycling reference'));
}
if((typeof x === 'object'&&typeof x !=='null')||typeof x ==='function'){
let then = x.then;
let called;
try{
if(typeof then ==='function'){
//因为promise或者thenable函数都是具有then属性的,所以可以放在一起处理
//then是函数,就调用x的then,其有两个参数,一个是resolvePromise,一个是rejectPromise
then.call(x,y=>{
//resolve和reject只能调用一次
if(called) return;
called = true;
//按照要求此处应该是执行resolve(y),但是可能会存在y还是promise的情况,所以此处要递归调用
//resolve(y);
resolvePromise(promise,y,resolve,reject);
},r=>{
if(called) return;
called = true;
reject(r);
})
}else{
//如果then不是一个函数,就返回x
resolve(x);
}
}catch(e){
if(called) return;
called = true;
//过程中如果出现了错误e,那么就用reject(e),返回结果
reject(e)
}
}else{
//如果x不是对象也不是函数,就直接返回x
resolve(x);
}
}
以上我们处理了then的返回值,那么如果Promise resolve的时候直接返回一个promise呢?如下:
new Promise((resolve,reject)=>{
resolve(new Promise((resolve,reject)=>{
resolve(1);
}))
})
根据要求 我们肯定是希望返回一个1,那么应该如何处理呢?
我们只需要在我们编写的resolve函数中判断以下value的值,如果是promise的实例,那么就去执行then方法,因为我们已经在then中处理了参数为promise的情况,所以问题就搞定了,见下图:
修改过后,让我们再来看一下控制台中执行上述代码的结果吧
得到了我们想要的1,搞定!
其他方法
Promises/A+规范只规定了promise和then的规则,并不强制实现其他方法,其他ES6相关的方法的实现在面试中也比较常问到,我们下篇文件再来实现!
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!