深刻理解Promise系列(一):手把手教你實(shí)現(xiàn)Promise(1)

是什么

Promise是一種對(duì)異步操作的封裝,主流的規(guī)范是Promise/A+。
Promise可以使得異步代碼層次清晰,便于理解,且更加容易維護(hù)。

追求的效果

那么我們的Promise將要實(shí)現(xiàn)怎樣的效果呢?舉個(gè)簡(jiǎn)單的例子如下:

function p1() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(1);
    }, 1000);
  });
}
function p2(value) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(2 + value);
    }, 1000);
  });
}
p1().then(function(res) {
  console.log(res); // 1000ms后輸出1
  return Promise.resolve(res); // 顯式的return一個(gè)Promise對(duì)象
}).then(p2).then(function(res) {
  console.log(res); // 再過1000ms后輸出3
});

我們手寫的Promise最終也可以實(shí)現(xiàn)這個(gè)效果

最基本的構(gòu)建

function Promise(fn) {
  let value = null; // 異步函數(shù)執(zhí)行后的結(jié)果
  let deferred; // 異步函數(shù)執(zhí)行后,真正要執(zhí)行的回調(diào)函數(shù)
  this.then = function(onFulfilled) {
    deferred = onFulfilled;
  }
  function resolve(newValue) {
    value = newValue;
    deferred(value);
  }
  fn(resolve);
}

代碼很少,也很容易理解:

  • 創(chuàng)建Promise實(shí)例的參數(shù)是fn,并將其內(nèi)部的resolve方法作為參數(shù)傳遞給異步函數(shù)
  • Promise的then方法用于注冊(cè)回調(diào)函數(shù),即賦值給內(nèi)部的deferred
  • 當(dāng)異步函數(shù)回調(diào)成功后,會(huì)將結(jié)果作為參數(shù)來執(zhí)行resolve,而實(shí)際上是執(zhí)行deferred函數(shù)
  • 這樣就實(shí)現(xiàn)了在恰當(dāng)?shù)臅r(shí)機(jī)(異步函數(shù)回調(diào)成功后),執(zhí)行恰當(dāng)?shù)幕卣{(diào)(then注冊(cè)的回調(diào)方法)

改進(jìn)1

目前的代碼只能注冊(cè)一個(gè)回調(diào)方法,這顯然不符合我們的預(yù)期,所以將內(nèi)部的deferred修改為deferreds數(shù)組,相應(yīng)的執(zhí)行resolve時(shí),也要遍歷deferreds數(shù)組依次執(zhí)行:

function Promise(fn) {
  let value = null;
  let deferreds = [];
  this.then = function(onFulfilled) {
    deferreds.push(onFulfilled);
  }
  function resolve(newValue) {
    value = newValue;
    deferreds.forEach((deferred) => {
      deferred(value);
    });
  }
  fn(resolve);
}

改進(jìn)2

實(shí)現(xiàn)then的鏈?zhǔn)秸{(diào)用,非常簡(jiǎn)單:

  this.then = function(onFulfilled) {
    deferreds.push(onFulfilled);
    return this;
  }

這樣就可以實(shí)現(xiàn):

p1().then(function(res) {
  // do sth. with res
}).then(function(res) {
  // do sth. else with res
});

延時(shí)resolve

目前的Promise有一個(gè)bug,假如fn中所包含的是同步代碼,則resolve會(huì)立即執(zhí)行,此時(shí)then還沒有注冊(cè)回調(diào)函數(shù),內(nèi)部的deferreds為空數(shù)組,所以回調(diào)函數(shù)不會(huì)如預(yù)期一樣執(zhí)行。
所以,為resolve添加一個(gè)延時(shí):

  function resolve(newValue) {
    value = newValue;
    setTimeout(() => {
      deferreds.forEach((deferred) => {
        deferred(value);
      });
    }, 0);
  }

以上保證了resolve于then注冊(cè)回調(diào)函數(shù)之后執(zhí)行。

引入狀態(tài)

目前還存在一點(diǎn)問題,現(xiàn)在用then注冊(cè)回調(diào)函數(shù)的行為都是在異步操作成功之前,一旦異步操作已經(jīng)成功后,內(nèi)部resolve已經(jīng)執(zhí)行完畢,再用then方法注冊(cè)回調(diào)函數(shù)就不會(huì)再執(zhí)行了。
想要解決這個(gè)問題,需要引入規(guī)范中的三個(gè)狀態(tài):pending、fulfilled、rejected,它們之間的關(guān)系是:


Promise的三種狀態(tài)

引入狀態(tài)后的代碼:

function Promise(fn) {
  let state = "pending";
  let value = null;
  let deferreds = [];
  this.then = function(onFulfilled) {
    // state若為pending則將onFulfilled加入隊(duì)列
    if(state === "pending") {
      deferreds.push(onFulfilled);
      return this;
    }
    // state若為fulfilled則立即執(zhí)行onFulfilled
    onFulfilled(value);
    return this;
  }
  function resolve(newValue) {
    state = "fulfilled"; // 異步操作完成后將state置為fulfilled
    value = newValue;
    setTimeout(() => {
      deferreds.forEach((deferred) => {
        deferred(value);
      });
    }, 0);
  }
  fn(resolve);
}

異步操作成功之后,state會(huì)變成fulfilled,這之后then注冊(cè)的回調(diào)函數(shù)都會(huì)立即執(zhí)行。

以上基本實(shí)現(xiàn)了Promise的所有基礎(chǔ)功能,但真正魔幻般的部分尚未開始,詳見下篇~

參考資料剖析 Promise 之基礎(chǔ)篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容