From 218934fa2ed1c702a6d3923d2aa2cc6b43c48684 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:43:23 -0500 Subject: initial commit --- .../web/javascript/guide/using_promises/index.html | 256 +++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 files/zh-tw/web/javascript/guide/using_promises/index.html (limited to 'files/zh-tw/web/javascript/guide/using_promises') diff --git a/files/zh-tw/web/javascript/guide/using_promises/index.html b/files/zh-tw/web/javascript/guide/using_promises/index.html new file mode 100644 index 0000000000..1df6376ffd --- /dev/null +++ b/files/zh-tw/web/javascript/guide/using_promises/index.html @@ -0,0 +1,256 @@ +--- +title: 使用 Promise +slug: Web/JavaScript/Guide/Using_promises +translation_of: Web/JavaScript/Guide/Using_promises +--- +
{{jsSidebar("JavaScript Guide")}}
+ +

{{jsxref("Promise")}} 是一個表示非同步運算的最終完成或失敗的物件。由於多數人使用預建立的 Promise,這個導覽會先講解回傳 Promise 的使用方式,之後再介紹如何建立。

+ +

基本上,一個 Promise 是一個根據附加給他的 Callback 回傳的物件,以取代傳遞 Callback 到這個函數。

+ +

舉例來說,下方的範例若用舊方式應該會有兩個 Callback,並根據成功或失敗來決定使用哪個:

+ +
function successCallback(result) {
+  console.log("It succeeded with " + result);
+}
+
+function failureCallback(error) {
+  console.log("It failed with " + error);
+}
+
+doSomething(successCallback, failureCallback);
+
+ +

而新作法會回傳一個 Promise,這樣你就可以附加 Callback:

+ +
let promise = doSomething();
+promise.then(successCallback, failureCallback);
+ +

再簡單點:

+ +
doSomething().then(successCallback, failureCallback);
+ +

我們稱之為 非同步函數呼叫。這個做法有許多好處,我們接下來看看。

+ +

保證

+ +

不如舊做法,一個 Promise 有這些保證:

+ + + +

但 Promise 主要的立即好處是串連。

+ +

串連

+ +

有個常見的需求是依序呼叫兩個以上的非同步函數,我們稱之為建立 Promise 鏈

+ +

看看魔術:then 函數回傳一個新的 Promise,不同於原本。

+ +
let promise = doSomething();
+let promise2 = promise.then(successCallback, failureCallback);
+
+ +

+ +
let promise2 = doSomething().then(successCallback, failureCallback);
+
+ +

第二個 Promise 不只代表 doSomething() 完成,還有successCallbackfailureCallback ,這兩個非同步函數回傳另一個 Promise。如此一來,任何 Callback 附加給 promise2 會被排在 successCallbackfailureCallback 之後。

+ +

基本上,每個 Promise 代表著鏈中另外一個非同步函數的完成。

+ +

在古時候,多個非同步函數會使用 Callback 方式,導致波動拳問題:

+ +

(原文 Pyramid of Doom 查無中文翻譯,以較常見之波動拳取代)

+ +
doSomething(function(result) {
+  doSomethingElse(result, function(newResult) {
+    doThirdThing(newResult, function(finalResult) {
+      console.log('Got the final result: ' + finalResult);
+    }, failureCallback);
+  }, failureCallback);
+}, failureCallback);
+
+ +

有了新方法,我們附加 Callback 到回傳的 Promise 上,來製造 Promise 鏈

+ +
doSomething().then(function(result) {
+  return doSomethingElse(result);
+})
+.then(function(newResult) {
+  return doThirdThing(newResult);
+})
+.then(function(finalResult) {
+  console.log('Got the final result: ' + finalResult);
+})
+.catch(failureCallback);
+
+ +

then 的函數是選用的,以及 catch(failureCallback)then(null, failureCallback) 的簡寫。你也許會想用箭頭函數取代:

+ +
doSomething()
+.then(result => doSomethingElse(result))
+.then(newResult => doThirdThing(newResult))
+.then(finalResult => {
+  console.log(`Got the final result: ${finalResult}`);
+})
+.catch(failureCallback);
+
+ +

注意:永遠要回傳結果,否則 Callback 不會獲得前一個 Promise 的結果。

+ +

獲錯後串接

+ +

失敗的串接是可行的,也就是說 catch 會非常好用,即使鏈中出錯。看看這個範例:

+ +
new Promise((resolve, reject) => {
+    console.log('Initial');
+
+    resolve();
+})
+.then(() => {
+    throw new Error('Something failed');
+
+    console.log('Do this');
+})
+.catch(() => {
+    console.log('Do that');
+})
+.then(() => {
+    console.log('Do this whatever happened before');
+});
+
+ +

他會輸出:

+ +
Initial
+Do that
+Do this whatever happened before
+
+ +

注意「Do this」沒有被輸出,因為「Something failed」錯誤導致拒絕。

+ +

錯誤傳遞

+ +

在波動拳狀況中,你可能會看到三次 failureCallback ,在 Promise 鏈中只需要在尾端使用一次:

+ +
doSomething()
+.then(result => doSomethingElse(result))
+.then(newResult => doThirdThing(newResult))
+.then(finalResult => console.log(`Got the final result: ${finalResult}`))
+.catch(failureCallback);
+
+ +

基本上,一個 Promise 鏈遇到錯誤時會往下尋找 Catch 處理器。這是經過模組化的非同步程式:

+ +
try {
+  let result = syncDoSomething();
+  let newResult = syncDoSomethingElse(result);
+  let finalResult = syncDoThirdThing(newResult);
+  console.log(`Got the final result: ${finalResult}`);
+} catch(error) {
+  failureCallback(error);
+}
+
+ +

在 ECMAScript 2017 中,在有 async/await 語法糖的同步程式達到高峰:

+ +
async function foo() {
+  try {
+    let result = await doSomething();
+    let newResult = await doSomethingElse(result);
+    let finalResult = await doThirdThing(newResult);
+    console.log(`Got the final result: ${finalResult}`);
+  } catch(error) {
+    failureCallback(error);
+  }
+}
+
+ +

這基於 Promise,例如 doSomething()和之前一樣。你可以閱讀在這裡閱讀更多。

+ +

Promise 藉由捕捉所有錯誤,包含例外和程式錯誤,解決了 Callback 地獄的缺點。這是非同步運算的基本特性。

+ +

在舊有 API 上建立 Promise

+ +

{{jsxref("Promise")}} 可以透過建構子建立。所以用建構子包裹舊的 API即可。

+ +

在理想情況,所有非同步函數都會回傳 Promise,然而許多 API 仍然用舊的方式來傳遞成功、失敗 Callback,有個典型的例子是{{domxref("WindowTimers.setTimeout", "setTimeout()")}} :

+ +
setTimeout(() => saySomething("10 seconds passed"), 10000);
+
+ +

混合古代 Callback 和 Promise 是有問題的。如果 saySomething 失敗或有程式錯誤,那不會有任何錯誤被捕捉。

+ +

幸運地,我們可以用 Promise 包裹他,最好盡可能的在最底層包裹,並永遠不要再直接呼叫他們:

+ +
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+wait(10000).then(() => saySomething("10 seconds")).catch(failureCallback);
+
+ +

基本上,Promise 建構子需要一個運作函數來正規地處理或拒絕 Promise。但因為 setTimeout 不會失敗,所以我們捨棄 reject。

+ +

組合

+ +

{{jsxref("Promise.resolve()")}} 和 {{jsxref("Promise.reject()")}} 是用來正規地建立已經處理或拒絕的 Promise。他們在某些情況特別有用。

+ +

{{jsxref("Promise.all()")}} 和 {{jsxref("Promise.race()")}} 是兩個組合工具用於使 Promise 平行運作。

+ +

連續關聯是可行的,這是極簡 JavaScript 範例:

+ +
[func1, func2].reduce((p, f) => p.then(f), Promise.resolve());
+
+ +

基本上,我們摺疊(Reduce)一個非同步函數陣列成一個 Promise 鏈:Promise.resolve().then(func1).then(func2);

+ +

這可以用可重用的構成函數完成,通常用函數式編程:

+ +
let applyAsync = (acc,val) => acc.then(val);
+let composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));
+ +

composeAsync 接受任何數量的函數作為參數,並回傳一個接受一個初始值用來傳給組合的新函數。這個好處是無論其中函數是非同步或否,都會保證用正確的順序執行:

+ +
let transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
+transformData(data);
+
+ +

ECMAScript 2017 中連續組合利用 async/await 更簡單:

+ +
for (let f of [func1, func2]) {
+  await f();
+}
+
+ +

計時

+ +

為了避免意外,傳給 then 的函數不會被同步地呼叫,即使是完成的 Promise:

+ +
Promise.resolve().then(() => console.log(2));
+console.log(1); // 1, 2
+
+ +

被傳入的函數會被放在子任務佇列而非立即執行,因此他會在當前的事件迴圈結束、佇列清空時執行,例如:

+ +
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
+
+wait().then(() => console.log(4));
+Promise.resolve().then(() => console.log(2)).then(() => console.log(3));
+console.log(1); // 1, 2, 3, 4
+
+ +

看更多

+ + -- cgit v1.2.3-54-g00ecf