最最最通俗易懂的promise手寫系列(二)- 鏈式呼叫

JuoJuo發表於2019-02-28

前言

上一次我們寫完了Promise的基礎功能,明白了promise為何能在任務成功的時候,調成功的回撥函式,為何在任務失敗的時候調失敗的回撥函式.今天就把最麻煩的promise鏈式呼叫給搞定吧.

附上上篇文章的連結:最最最通俗易懂的promise手寫系列(一)

再附上上次的程式碼吧,以免翻來翻去麻煩.

  function Promise(executor) {
    let self = this;
    self.value = undefined;
    self.reason = undefined;
    self.status = `pending`;

    self.onFulFilledCallbacks = [];
    self.onRejectedCallbacks = [];

    function resolve(value) {
        if (self.status === `pending`) {
            self.value = value;
            self.status = `resolved`

            self.onFulFilledCallbacks.forEach(onFulFilled => {
                onFulFilled(self.value)
            });
        }
    }
    function reject(reason) {
        if (self.status === `pending`) {
            self.reason = reason;
            self.status = `rejected`;
            self.onRejectedCallbacks.forEach(onRejected => {
                onRejected(self.reason)
            });
        }
    }

    try {
        executor(resolve, reject);
    } catch (error) {
        reject(error)
    }
}

Promise.prototype.then = function (onFulFilled, onRejected) {
    if (this.status === `pending`) {
        this.onFulFilledCallbacks.push(() => {
            onFulFilled(this.value)
        }); 
        this.onRejectedCallbacks.push(() => {
            onRejected(this.reason)
        })
    } else if (this.status === `resolved`) {
        onFulFilled(this.value);
    } else if (this.status === `rejected`) {
        onRejected(this.reason);
    }
}
複製程式碼

我們要完成的目標:

let p = new Promise(function (resove, reject) {
  setTimeout(() => {
    console.log(`任務執行完了`);
    resove()
  },1500)
});

p.then(function (value) {
      console.log(`第一個成功回撥`)
    },function () {})
  .then(function () {
      console.log(`第二個成功回撥`)
    }, function () {});
    
輸出如下:
任務執行完了
第一個成功回撥
第二個成功回撥
複製程式碼
  • 我們都熟悉Jquery,它的鏈式呼叫是用rerun this來做的,可是這裡卻不行,原因文章末尾再解釋。
    我們採取返回一個新的promise物件來實現鏈式呼叫.
  • 意思也就是p.then()返回一個新promise物件.
//給這個函式加個返回值,返回值就是一個新new的promise物件
Promise.prototype.then = function (onFulFilled, onRejected) {
    let p2 = new Promise((resolve, reject) => {});
    
    if (this.status === `pending`) {
        this.onFulFilledCallbacks.push(() => {
            onFulFilled(this.value)
        }); 
        this.onRejectedCallbacks.push(() => {
            onRejected(this.reason)
        })
    } else if (this.status === `resolved`) {
        onFulFilled(this.value);
    } else if (this.status === `rejected`) {
        onRejected(this.reason);
    }
    return p2;
}
複製程式碼
  • 加了兩行程式碼,我們測試一下輸出結果:
    任務執行完了
    第一個成功回撥
  • 根據上一次我們所講的可以總結出如下結論:真正觸發onFulFilledCallbacks裡所儲存的回撥函式只有兩個地方:
  1. resolve被呼叫的時候.(resolve是使用者呼叫的,因為使用者當然知道哪種邏輯算任務成功,哪種邏輯算任務失敗,例如我們上一章的例子,隨機數大於5我就認為是成功的.)
  2. then被呼叫的時候.(then也是使用者呼叫的,promise裡的任務執行完了後要做啥)

這裡我們理一下流程:在我們new Promise的時候,executor就同步的執行了,根據executor裡有無非同步操作分一下兩種情況:

1.有非同步操作,如我們的例子裡,有setTimout,延時1.5s後列印。

  • executor函式執行完,p也就完成了new的過程。(此時還不會列印輸出任務執行完了,因為setTimout是非同步的)
  • 拿到p物件後,程式碼接著執行p.then,此時很明顯狀態是pending,我們會把使用者在then裡傳的回撥函式存起來(onFulFilledCallbacks),在1.5s後,setTimeout裡呼叫resovle的時候,會遍歷onFulFilledCallbacks,執行之前then裡傳的函式
  • 這也就是我們之前為什麼要給promise加三個狀態的原因(等待pending 成功resolve 失敗rejected),害怕裡面有非同步任務。
    不管如何非同步,使用者總是知道程式碼走到哪兒算是成功了(在成功的地方調resolve),程式碼走到哪兒算是出異常了(在失敗的地方調reject)這是規矩,使用Promise必須遵守的規矩。

2.沒有非同步操作,如我們的例子裡,有setTimout,延時1.5s後列印。

  • 這個就簡單了,跟著程式碼順序看就行了,執行executor直接就會調resolve,then的會後,任務已經完成,當即執行使用者傳的回撥函式就行了

回到我們要解決的問題剛只是輸出了第一個成功回撥,因為【p2】的status是pending呀(我們在內部自己new的Promise,內部沒有任何要執行的東西,沒有調resolve,那我們就調一下唄).

//給這個函式加個返回值,返回值就是一個新new的promise物件
Promise.prototype.then = function (onFulFilled, onRejected) {
    let p2 = new Promise((resolve, reject) => { resove() });
    
    if (this.status === `pending`) {
        this.onFulFilledCallbacks.push(() => {
            onFulFilled(this.value)
        }); 
        this.onRejectedCallbacks.push(() => {
            onRejected(this.reason)
        })
    } else if (this.status === `resolved`) {
        onFulFilled(this.value);
    } else if (this.status === `rejected`) {
        onRejected(this.reason);
    }
    return p2;
}
複製程式碼
  • 我們測試一下輸出結果:
    第二個成功回撥
    任務執行完了
    第一個成功回撥
  • 這一會倒是執行了,可是順序亂了,原因:p.then拿到p2,p2立刻就resolve了,當程式碼走到p2.then自然就是直接走到this.status === `resolved`,執行傳入的函式了.等1.5s後第一個promsie任務完成,列印任務執行完了呼叫第一個的resolve,第一個成功回撥
  • 我們想要的是自己控制順序,看直白一點就是我們得控制在第一個Promise也就是P任務執行完了,才能調P2.resove,那就直接在第一個Promise回撥函式執行完了我們再調P2.resove吧
//給這個函式加個返回值,返回值就是一個新new的promise物件
Promise.prototype.then = function (onFulFilled, onRejected) {
  //p2的resolve在裡面,外面拿不到,只有這樣很賤的給在外面記下來了
  let p2Resolve ;
  let p2Reject;
  let p2 = new Promise((resolve, reject) => {
    p2Resolve = resolve;
    p2Reject = reject;
  });

  if (this.status === `pending`) {
    this.onFulFilledCallbacks.push(() => {
      onFulFilled(this.value)
      p2Resolve()
    });
    this.onRejectedCallbacks.push(() => {
      onRejected(this.reason)
      p2Reject()
    })
  } else if (this.status === `resolved`) {
    onFulFilled(this.value);
    p2Resolve()
  } else if (this.status === `rejected`) {
    onRejected(this.reason);
    p2Reject()
  }

  return p2;
}
複製程式碼

輸出如下:
任務執行完了
第一個成功回撥
第二個成功回撥

結語

我們剩下onFulFilled返回值的功能沒做,下次子再來.
感謝各位觀眾.

相關文章