美文网首页
学习 Javascript 之手写Promise

学习 Javascript 之手写Promise

作者: 呱啦哒 | 来源:发表于2022-06-22 11:54 被阅读0次

    根据自己学习Promise的理解写的, 不知道对不对,看运行结果是没有问题的
    paotui.js

    let PAOTUI_ID = 1;//给每个Paotui实例对象一个id,便于调试跟踪区别
    
    class Paotui {
        constructor(confun){
            this.id = PAOTUI_ID++;
            this.status = 'pending';//状态 pending fulfilled rejected
            this.result = undefined;
            this._objthen = null;//存储当前对象.then产生的对象
            this._callback = [];//存储当前对象的then方法的回调函数,便于后面回调 0:成功函数,1:失败函数,2:catch函数 3:finally函数 4:数组,存储其他回调函数
            
            if(confun){
                //如果有构造函数就执行Paotui的构造函数,
                //这里放到setTimeout中执行,主要是确保在执行_resolve前this._callback里面已经有then函数了
                //因为执行_resolve时就会去回调执行_callback中的函数
                //不知道这里setTimeout是否多余??????????
                setTimeout(()=> {
                    confun(this._resolve.bind(this),this._reject.bind(this));
                }, 0)
                
            }
        }
    
        //在状态变化时执行其他回调函数
        othercallback() {
            
            if(this._callback[4] === undefined) return;
    
            while(this._callback[4].length > 0) {
                this._directExecution(this._callback[4].shift());
            }
    
        }
        
        //回调函数 包括0:成功函数,1:失败函数,2:catch函数 3:finally函数
        runcallback(status, result) {
            //最后一个对象_objthen为null, 这里先判断一下
            if(!this) return;
            
            //如果上一个对象的status为_resolved, 这里也执行成功函数, 就是_callback[0]中的内容
            if(status === 'fulfilled') {
    
                //调用_resolve 改变当前实例对象的状态, 状态跟着上个实例来
                this._resolve.call(this, result);
                
                //执行then方法传入的成功后执行函数, 
                //_callback[0] 这个函数有3种返回值 1 undefined 2 返回一个Paotui对象 3 其他基本类型的值或者非Paotui对象的值
                const callresult = this._directExecution(this._callback[0], result) || result;
                
                //如果返回值是一个新的Paotui对象, 就把当前对象的_objthen挂载到新对象上,这样新对象的状态改变后就可以自动回调then的_callback了
                if(callresult instanceof Paotui) {
    
                    callresult._objthen = this._objthen;
                    //this._objthen = null;
    
                } else {
                    
                    //如果是基本类型或者非Paotui对象
                    //这种对象是不会调用Paotui对象的构造函数的_resolve函数,就是说会一直是pending状态
                    //既然不是Paotui对象,也就不存在异步操作, 所以我们直接手动调用_callback函数      
                    this._executionRuncallbackBySetTimeout.call(this, callresult);
                }
                
            } else if(status === 'rejected') {
                
                this._reject.call(this, result);
    
                const callresult = this._directExecution(this._callback[1], result) || result;            
    
                //如果有catch的话
                this._directExecution(this._callback[2], result);
                
                this._executionRuncallbackBySetTimeout.call(this,callresult);
                
    
            }
    
            //不管状态是什么,回调finally(如果有的话) finally不支持传参数
            this._directExecution(this._callback[3]);
        }
    
        //用setTimeout的方式调用runcallback函数
        _executionRuncallbackBySetTimeout(callresult) {
            if(this._objthen) {
                setTimeout(()=>{
                    this.runcallback.call(this._objthen, this.status, callresult);
                }, 0);
            }
        }
        
        //then方法,直接创建一个新的Paotui对象实例,然后返回这个对象
        //后面的回调函数会去修改这个对象,只要保持这个对象不指向其他地方就行
        then(resfun, rejfun){
            const newpt = new Paotui();
    
            //把新创建的Paotui对象地址保存的当前对象的_objthen变量中
            this._objthen = newpt;
    
            //把resfun 和 rejfun 两个函数放入_callback数组的0,1位置, 在后面当前对象状态被改变时执行
            newpt._callback[0] = resfun;
            newpt._callback[1] = rejfun;
    
            return newpt;
        }
        
        //把状态修改为fulfilled
        _resolve(result) {
    
            if(this.status !== 'pending') return;
            this.status = 'fulfilled';
            this.result = result;
    
            //如果this._callback[0] === undefined && this._callback[1] === undefined说明这个对象是由构造函数new创建的
            //因为由then中创建的Paotui对象_callback中至少会有一个回调函数
            //当前实例状态一旦改变,就去调用关联的then对象的_callback
            //then中创建的的对象这里不管, 因为在runcallback中会去回调
            if(this._callback[0] === undefined && this._callback[1] === undefined) {
                //这里用call传入当前对象的then创建的对象绑定为runcallback的this, 传入当前的状态和result
                this.runcallback.call(this._objthen, 'fulfilled', result);
            }
    
            this.othercallback();
        }
        
        //把状态修改为rejected
        _reject(result){
            if(this.status !== 'pending') return;
    
            this.status = 'rejected';
            this.result = result;
    
            if(this._callback[0] === undefined && this._callback[1] === undefined) {
                //这里用call传入当前对象的then创建的对象绑定为runcallback的this, 传入当前的状态和result
                this.runcallback.call(this._objthen, 'rejected', result);
            }
    
            this.othercallback();
        }
    
        //直接执行函数, 不涉及this
        _directExecution(objfun, val) {
            
            if(typeof objfun === 'function') {
                const exeresult = val ? objfun(val) : objfun();
                objfun = null;            
                return exeresult;
            }        
    
        }
    
        catch(errorFun) {
            this._callback[2]= errorFun;
            return this;
        }
    
        finally(finallyFun) {
            
            this._callback[3] = finallyFun;
    
            //也可以不返回了, finally已经是最后一个了
            return this;
        }
    
        
        //传入Paotui实例直接返回, 否则返回一个状态为fulfilled, 结果为val的Paotui实例
        static resolve(val) {
            if(val instanceof Paotui) {
                return val;
            }
            
            // if(val?.then) {  搞不清传入一个含有then方法的对象要怎么做
            //     const newpt = new Paotui()
            // }
            
            return new Paotui((res,rej)=>{res(val);});
        }
    
        //paotuos为一个Paotui实例数组,如果里面有非Paotui实例,就用resolve转成Paotui实例
        static all(paotuos) {
    
            const allCallback = function(paotuos) {
                if(this.status !== 'pending') return;
                let arrayResult = [];
                for(let it of paotuos) {
                    if(it.status === 'rejected') {
                        this._reject.call(this, it.result);
                        return;
                    } else if(it.status === 'fulfilled') {
                        arrayResult.push(it.result);
                    }
                    
                }
                if(arrayResult.length === paotuos.length) {
                    this._resolve.call(this, arrayResult);                
                }
            }
    
            
    
            const newpt = new Paotui();
            allCallback.call(newpt, paotuos);
            Paotui.bindothecallback(paotuos, allCallback, newpt);
            return newpt;
        }
    
    
        static race(paotuos) {
    
            const raceCallback = function(paotuos) {
                if(this.status !== 'pending') return;
    
                for(let it of paotuos) {
                    if(it.status === 'fulfilled') {
                        this._resolve.call(this, it.result);
                        return;
                    } else if(it.status === 'rejected') {
                        this._reject.call(this, it.result);
                        return;
                    }
                }
            }
    
            const newpt = new Paotui();
            raceCallback.call(newpt, paotuos);
            Paotui.bindothecallback(paotuos, raceCallback, newpt);
            return newpt;
        }
    
        //绑定其他回调函数
        static bindothecallback(paotuos, callbackfun, newpt) {
            paotuos.forEach((it, index) => {
                if(!(it instanceof Paotui)) {
                    paotuos[index] = Paotui.resolve(it);
                }
                if(paotuos[index]._callback[4] === undefined) paotuos[index]._callback[4] = [];
                paotuos[index]._callback[4].push(callbackfun.bind(newpt, paotuos));
            });
        }
    
    }
    
    
    
    
    

    测试用index.html

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>跑腿</title>
        <script src="http://upcdn.b0.upaiyun.com/libs/jquery/jquery-2.0.0.min.js"></script>
        <script src="./paotui.js"></script>
    
      </head>
      <body>
        从<select id="startfilename"><option>data1.json</option><option>data2.json</option><option>data3.json</option><option>data4.json</option></select>文件开始
        <br>用<select id="witchpromise"><option value="Paotui">Paotui</option><option value="Promise">Promise</option></select>方法
        <br>
        <br>
        <button onclick="testThen();">测试Paotui.then</button>
    
        <button onclick="testAll();">测试Paotui.all</button>
    
        <button onclick="testRace();">测试Paotui.race</button>
        <script>
    
          const witchpromise = {"Paotui":Paotui, "Promise":Promise}
    
          
          const getJsonData = function(url, data) {
              const promise = witchpromise[$("#witchpromise").val()];
              
              return new promise((resolve, reject) => {
    
                  $.ajax({ type:'GET', url:url,  dataType:"json", data:data,
                    success: function(res) {
                      const numrmd = Math.round(Math.random()*10);
                      
                      if(numrmd <= 9) {
                        setTimeout(() =>{
                          resolve(res);
                        }, Math.round(Math.random()*10)*100);
                        
                        //reject("第二次改执行reject" + url);
                      } else {
                        reject("哦或,随机reject了,随机数 = " + numrmd + " 文件名: " + url);
                      }
                      
                  },
                  error:function(res) {
                    reject(res);
                  }
                });
    
            });
          }
    
      function testThen() {
        console.log("******************开始了******************")
        const filename = $("#startfilename").val();
    
        console.log(`从${filename}开始`);
        console.log(`使用${$('#witchpromise').val()}方法`);
        
        const jd = getJsonData('./' + filename);
        
        jd.then((val)=>{      
    
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " 第一层的结果: " , val);
          const pp =  getJsonData('./' + val.filename);
    
          return pp;
        })
        .then((val)=>{
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " 第二层的结果: " , val);
          const pp =  getJsonData('./' + val.filename);
    
          return pp;
        }).then((val)=>{      
    
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " 第三层的结果: " , val);
          return "这是最后一个结果了,OVER"
        })
        .then((val)=>{      
    
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " 最后一层的结果: " , val);
          
        }).catch((errmsg) =>{
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " catch的结果: " , errmsg);
        }).finally(()=>{
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " 执行finally进行收尾");
        });
    
        
        console.log("******************结束了******************")
      }
    
      function testAll() {
        const promises = [7,6,5,4,3,2,1].map(function (id) {
          return getJsonData('./data' + id + ".json");
        });
        promises.push('file888.json');
        console.log('Time:' + String(new Date().getTime()).substr(-5) , promises);
        const PaoORPrimise = witchpromise[$("#witchpromise").val()];
    
        const pt = PaoORPrimise.all(promises);
        
        pt.then((val)=>{
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " all 成功了 => " , val);
        }).catch((val)=>{
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " all 失败了 => " , val);
        });
    
        console.log(pt);
    
      }
    
      function testRace() {
        const promises = [7,6,5,4,3,2,1].map(function (id) {
          return getJsonData('./data' + id + ".json");
        });
        console.log('Time:' + String(new Date().getTime()).substr(-5) , promises);
        const PaoORPrimise = witchpromise[$("#witchpromise").val()];
    
        const pt = PaoORPrimise.race(promises);
        
        pt.then((val)=>{
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " race 成功了 => " , val);
        }).catch((val)=>{
          console.log('Time:' + String(new Date().getTime()).substr(-5) + " race 失败了 => " , val);
        });
    
        console.log(pt);
    
      }
      
    
        </script>
      </body>
    </html>
    
    
    
    
    
    
    

    data1.json

    {
        "filename":"data3.json"
    }
    

    data2.json

    {
        "filename":"data1.json"
    }
    

    data3.json

    {
        "filename":"data2.json"
    }
    

    执行结果:

    开始了
    结束了
    第一层的结果:  {filename: 'data2.json'}
    第二层的结果:  {filename: 'data1.json'}
    第三层的结果:  {filename: 'data3.json'}
    最后一层的结果:  这是最后一个结果了,OVER
    

    相关文章

      网友评论

          本文标题:学习 Javascript 之手写Promise

          本文链接:https://www.haomeiwen.com/subject/prtuvrtx.html