最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手写一个符合Promises/A+规范的Promise

    正文概述 掘金(miniYM)   2021-01-31   497

    Promise的出现解决了js异步操作只能用嵌套回调来实现的缺憾。由于ES6提供的Promise在IE中无法使用,所以我们有必要掌握Promise的实现方式,上篇文章已经分析过ES6中Promise的用法,感兴趣的可以移步这里。

    本文主要基于Promises/A+来分析如何实现一个符合规范的Promise。

    基本要求

    先来看几个术语:

    手写一个符合Promises/A+规范的Promise

    promise:表示是一个行为符合Promises/A+的,具有then方法的对象或者函数。

    thenable:表示是一个具有then方法的对象或者函数。

    value:任何合法的JavaScript的值,包括undefined、thenable或者promise。

    exception:用then语句抛出来的值。

    reason:表示拒绝Promise的原因。

    下面来看要求:

    手写一个符合Promises/A+规范的Promise

    Promise的状态必须是以下三者中的其中一个:pending、fulfilled、rejected

    当promise处于pending状态:可以被转换到fulfilled或者reject其中一个;

    当promise处于fulfilled状态:不可过渡到其他状态,并且必须有一个value且该值不能被改变

    当promise处于rejected状态:不可过渡到其他状态,并且必须有一个reason且该值不能被改变

    手写一个符合Promises/A+规范的Promise

    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);
           })
        }
      }
    }
    

    链式调用、值的穿透

    手写一个符合Promises/A+规范的Promise

    我们再来继续看一下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参数

    手写一个符合Promises/A+规范的Promise

    • 如果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);
        }))
    })
    

    手写一个符合Promises/A+规范的Promise

    根据要求 我们肯定是希望返回一个1,那么应该如何处理呢?

    我们只需要在我们编写的resolve函数中判断以下value的值,如果是promise的实例,那么就去执行then方法,因为我们已经在then中处理了参数为promise的情况,所以问题就搞定了,见下图:手写一个符合Promises/A+规范的Promise

    修改过后,让我们再来看一下控制台中执行上述代码的结果吧

    手写一个符合Promises/A+规范的Promise

    得到了我们想要的1,搞定!

    其他方法

    Promises/A+规范只规定了promise和then的规则,并不强制实现其他方法,其他ES6相关的方法的实现在面试中也比较常问到,我们下篇文件再来实现!


    起源地下载网 » 手写一个符合Promises/A+规范的Promise

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元