自定义实现Promise

408 阅读2分钟

第一版

class MyPromise{
    constructor(excutor){
        this.state = "pending";
        this.resolveCallbacks = [];
        this.rejectCallbacks = [];
        let resolve = (value)=>{
            if(this.state==="pending"){
                this.state = "resolved";
                this.resolveCallbacks.forEach(fn=>fn(value));
            }
        };
        let reject = (reason)=>{
            if(this.state==="pending"){
                this.state = "rejected";
                this.rejectCallbacks.forEach(fn=>fn(reason));
            }
        };
        try{
            excutor(resolve, reject);
        }catch(e){
            console.log("excutor error", e);
            reject(e);
        }
    }
    then(resolve, reject){
        this.resolveCallbacks.push(resolve);
        this.rejectCallbacks.push(reject);
    }
}

getAjax("http://localhost:8081/getAPITest").then(res=>{
    console.log(res);
}, error=>{
    console.log("error", error);
})
function getAjax(url){
    return new MyPromise((resolve, reject)=>{
        let xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.onreadystatechange = ()=>{
            if(xhr.readyState !==4){
                return;
            }
            if(xhr.status ===200){
                resolve(xhr.response);
            }else{
                reject(new Error(xhr.status));
            }
        }
        xhr.send();
    })
}

第一版程序在执行有ajax异步请求的时候是没有问题的。但是在执行同步代码的时候,then里面定义的方法并不会执行:

  let test = new MyPromise((resolve, reject)=>{
      console.log("start");
      resolve("value");
  });
  test.then(res=>{
  	  //并不会执行
      console.log(res);
  })

为什么呢?这就要从执行顺序说起了。我们 new MyPromise 的时候,会执行传递进去的函数 (fn)(resolve, reject)=>{} 这个函数会被执行。同时 resolve("value"); 因为在定义的函数fn里面,也会被执行。没有ajax请求,会被同步执行。当我们调用 test.then()的时候也是同步代码,这个时候才会触发 this.resolveCallbacks.push(resolve); 但是 resolve 的方法已经被执行完了。then里面的回调就不会被调用了。
解决方案:

  • 方案一:
    MyPromise中定义then方法中分3种情况分别执行:
      then(resolve, reject){
        //说明都是同步代码,resolve方法已经执行完了
        if(this.state==="resolved"){
            resolve(this.value);
        }
        //说明都是同步代码,rejected方法已经执行完了
        if(this.state==="rejected"){
            reject(this.reason);
        }
        //ajax请求的时候是 then方法比resolve先执行的
        if(this.state==="pending"){
            this.resolveCallbacks.push(resolve);
            this.rejectCallbacks.push(reject);
        }
    }
    
    参考 juejin.cn/post/684490…
  • 方案二
    利用MutationObserver(微任务)来模仿 nextTick,改写resolve方法。MutationObserver会在所有同 步代码执行完毕后再执行,看下面代码:
      function nextTick(fn) {
         // 实现浏览器上的nextTick
         var counter = 1;
         var observer = new MutationObserver(fn);
         var textNode = document.createTextNode(String(counter));

         observer.observe(textNode, {
             characterData: true,
         });
         counter += 1;
         textNode.data = String(counter);
     }
     setTimeout(() => {
         console.log("setTimeout")
     }, 0);
     nextTick(()=>{
         console.log("nextTick");
     })

     console.log("wrap");

会输出:
同步代码: wrap 然后微任务:nextTick 最后宏任务 setTimeout
那么,我们利用微任务 MutationObserver 改写resolve方法,使得 resolve 最后执行。保证

   // then方法不变,还是负责把回调加入 resolveCallbacks 数组中
    class MyPromise{
    	...其他代码
         then(resolve, reject){
            this.resolveCallbacks.push(resolve);
            this.rejectCallbacks.push(reject);
        }
        //resolve 加入 nextTick模拟微任务 最后执行
        let resolve = (value)=>{
          if(this.state==="pending"){
              console.log("resolve")
              this.state = "resolved";
              this.value = value;
              nextTick(()=>{
                  this.resolveCallbacks.forEach(fn=>fn(value));
              })
          }
       }

  };
  
 function nextTick(fn) {
  // 实现浏览器上的nextTick
  var counter = 1;
  var observer = new MutationObserver(fn);
  var textNode = document.createTextNode(String(counter));

  observer.observe(textNode, {
      characterData: true,
  });
  counter += 1;
  textNode.data = String(counter);
}

参考:mp.weixin.qq.com/s/eoo3WLqGN…

第二版

方案一

class MyPromise{
    constructor(excutor){
        this.state = "pending";
        this.resolveCallbacks = [];
        this.rejectCallbacks = [];
        this.value = "";
        this.reason = "";
        let resolve = (value)=>{
            if(this.state==="pending"){
                this.state = "resolved";
                this.value = value;
                this.resolveCallbacks.forEach(fn=>fn(value));
            }
        };
        let reject = (reason)=>{
            if(this.state==="pending"){
                this.state = "rejected";
                this.reason = reason;
                this.rejectCallbacks.forEach(fn=>fn(reason));
            }
        };
        try{
            excutor(resolve, reject);
        }catch(e){
            console.log("excutor error", e);
            reject(e);
        }
    }
    then(resolve, reject){
        //说明都是同步代码,resolve方法已经执行完了
        if(this.state==="resolved"){
            resolve(this.value);
        }
        //说明都是同步代码,rejected方法已经执行完了
        if(this.state==="rejected"){
            reject(this.reason);
        }
        //ajax请求的时候是 then方法比resolve先执行的
        if(this.state==="pending"){
            this.resolveCallbacks.push(resolve);
            this.rejectCallbacks.push(reject);
        }
    }
}

方案二

class MyPromise{
    constructor(excutor){
        this.state = "pending";
        this.resolveCallbacks = [];
        this.rejectCallbacks = [];
        this.value = "";
        this.reason = "";
        let resolve = (value)=>{
            if(this.state==="pending"){
                console.log("resolve")
                this.state = "resolved";
                this.value = value;
                nextTick(()=>{
                    this.resolveCallbacks.forEach(fn=>fn(value));
                })
            }
        };
        let reject = (reason)=>{
            if(this.state==="pending"){
                this.state = "rejected";
                this.reason = reason;
                nextTick(()=>{
                    this.rejectCallbacks.forEach(fn=>fn(reason));
                })
            }
        };
        try{
            excutor(resolve, reject);
        }catch(e){
            console.log("excutor error", e);
            reject(e);
        }
    }
    then(resolve, reject){
        this.resolveCallbacks.push(resolve);
        this.rejectCallbacks.push(reject);
    }
}

function nextTick(fn) {
    // 实现浏览器上的nextTick
    var counter = 1;
    var observer = new MutationObserver(fn);
    var textNode = document.createTextNode(String(counter));
    observer.observe(textNode, {
        characterData: true,
    });
    counter += 1;
    textNode.data = String(counter);
}