学习 Javascript 之手写Promise

根据自己学习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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容