首頁  >  文章  >  web前端  >  需要真正理解的Promise

需要真正理解的Promise

coldplay.xixi
coldplay.xixi轉載
2020-09-07 13:30:062013瀏覽

需要真正理解的Promise

相關學習推薦:javascript學習教學課程

Promise 關於API 這塊大家應該可以熟練使用,但是和微任務相關的你可能還存在知識盲區。

前置知識

在開始正文前,我們先把本文涉及到的一些內容提前定個基調。

Promise 哪些 API 涉及了微任務?

Promise 中只有涉及到狀態變更後才需要被執行的回調才算是微任務,比如說thencatchfinally ,其他所有的程式碼執行都是巨集任務(同步執行)。

需要真正理解的Promise

上圖中藍色為同步執行,黃色為非同步執行(丟到微任務佇列中)。

這些微任務何時加入微任務佇列?

這個問題我們根據ecma 規範來看:

  • 如果此時Promise 狀態為pending,那麼成功或失敗的回呼會分別加入到[ [PromiseFulfillReactions]][[PromiseRejectReactions]] 中。如果你看過手寫 Promise 的程式碼的話,應該可以發現有兩個陣列來儲存這些回呼函數。

  • 如果此時 Promise 狀態為非 pending 時,回呼會變成 Promise Jobs,也就是微任務。

了解以上知識後,正片開始。

同一個then,不同的微任務執行

初級

Promise.resolve()
  .then(() => {    console.log("then1");    Promise.resolve().then(() => {      console.log("then1-1");
    });
  })
  .then(() => {    console.log("then2");
  });复制代码

以上程式碼大家應該可以得到正確的答案:then1 → then1 -1 → then2

雖然 then 是同步執行,且狀態也已經變更。但這不代表每次遇到then 時我們都需要把它的回調丟入微任務佇列中,而是等待then 的回呼執行完畢後再根據情況執行對應操作。

基於此,我們可以得到第一個結論:鍊式呼叫中,只有前一個then 的回呼執行完畢後,跟著的then 中的回呼才會被加入到微任務佇列。

中級

大家都知道了 Promise resolve 後,跟著的 then 中的回呼會馬上進入微任務佇列。

那麼以下程式碼你認為的輸出會是什麼?

let p = Promise.resolve();

p.then(() => {  console.log("then1");  Promise.resolve().then(() => {    console.log("then1-1");
  });
}).then(() => {  console.log("then1-2");
});

p.then(() => {  console.log("then2");
}); 
复制代码

按照一開始的認知我們不難得出 then2 會在 then1-1 後輸出,但是實際情況卻是相反的。

基於此我們得出第二個結論:每個鍊式呼叫的開端會先依序進入微任務佇列。

接下來我們換個寫法:

let p = Promise.resolve().then(() => {  console.log("then1");  Promise.resolve().then(() => {    console.log("then1-1");
  });
}).then(() => {  console.log("then2");
});

p.then(() => {  console.log("then3");
});复制代码

上述程式碼其實有個陷阱,then 每次都會回傳一個新的Promise,此時的p 已經不是Promise.resolve() 產生的,而是最後一個then 產生的,因此then3 應該是在then2 後印出來的。

順便我們也可以把先前得出的結論優化為:同一個 Promise 的每個鍊式呼叫的開端會先依序進入微任務佇列。

進階

以下大家可以猜猜 then1-2 何時會印出來?

Promise.resolve()
  .then(() => {    console.log("then1");    Promise.resolve()
      .then(() => {        console.log("then1-1");        return 1;
      })
      .then(() => {        console.log("then1-2");
      });
  })
  .then(() => {    console.log("then2");
  })
  .then(() => {    console.log("then3");
  })
  .then(() => {    console.log("then4");
  });复制代码

這題肯定是簡單的,記住第一個結論就能得到答案,以下是解析:

  • 第一次resolve 後面第一個then 的回呼進入微任務佇列並執行,印出then1

  • 第二次resolve 後內部第一個then 的回呼進入微任務佇列,此時外部第一個then 的回呼全部執行完畢,需要將外部的第二個 then 回呼也插入微任務佇列。

  • 執行微任務,列印then1-1then2,然後分別再將之後then 中的回調插入微任務佇列

  • 執行微任務,列印then1-2then3 ,之後的內容就不一一說明了

接下來我們把return 1 修改一下,結果可就大不相同啦:

Promise.resolve()
  .then(() => {    console.log("then1");    Promise.resolve()
      .then(() => {        console.log("then1-1");        return Promise.resolve();
      })
      .then(() => {        console.log("then1-2");
      });
  })
  .then(() => {    console.log("then2");
  })
  .then(() => {    console.log("then3");
  })
  .then(() => {    console.log("then4");
  });复制代码

當我們return Promise.resolve( ) 時,你猜猜看then1-2 會何時印了?

答案是最後一個才被印出來。

為什麼在 then 中分別 return 不同的東西,微任務的執行順序竟有如此大的變化?以下是筆者的解析。

PS:then 返回一个新的 Promise,并且会用这个 Promise 去 resolve 返回值,这个概念需要大家先了解一下。

根据 Promise A+ 规范

根据规范 2.3.2,如果 resolve 了一个 Promise,需要为其加上一个 thenresolve

if (x instanceof MyPromise) {  if (x.currentState === PENDING) {
  } else {
    x.then(resolve, reject);
  }  return;
}复制代码

上述代码节选自手写 Promise 实现。

那么根据 A+ 规范来说,如果我们在 then 中返回了 Promise.resolve 的话会多入队一次微任务,但是这个结论还是与实际不符的,因此我们还需要寻找其他权威的文档。

根据 ECMA - 262 规范

根据规范 25.6.1.3.2,当 Promise resolve 了一个 Promise 时,会产生一个NewPromiseResolveThenableJob,这是属于 Promise Jobs 中的一种,也就是微任务。

This Job uses the supplied thenable and its then method to resolve the given promise. This process must take place as a Job to ensure that the evaluation of the then method occurs after evaluation of any surrounding code has completed.

并且该 Jobs 还会调用一次 then 函数来 resolve Promise,这也就又生成了一次微任务。

这就是为什么会触发两次微任务的来源。

最后

文章到这里就完结了,大家有什么疑问都可以在评论区提出。

想了解更多编程学习,敬请关注php培训栏目!

以上是需要真正理解的Promise的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除