aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/web/javascript/guide/using_promises
diff options
context:
space:
mode:
authorPeter Bengtsson <mail@peterbe.com>2020-12-08 14:43:23 -0500
committerPeter Bengtsson <mail@peterbe.com>2020-12-08 14:43:23 -0500
commit218934fa2ed1c702a6d3923d2aa2cc6b43c48684 (patch)
treea9ef8ac1e1b8fe4207b6d64d3841bfb8990b6fd0 /files/zh-tw/web/javascript/guide/using_promises
parent074785cea106179cb3305637055ab0a009ca74f2 (diff)
downloadtranslated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.gz
translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.tar.bz2
translated-content-218934fa2ed1c702a6d3923d2aa2cc6b43c48684.zip
initial commit
Diffstat (limited to 'files/zh-tw/web/javascript/guide/using_promises')
-rw-r--r--files/zh-tw/web/javascript/guide/using_promises/index.html256
1 files changed, 256 insertions, 0 deletions
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
+---
+<div>{{jsSidebar("JavaScript Guide")}}</div>
+
+<p>{{jsxref("Promise")}} 是一個表示非同步運算的最終完成或失敗的物件。由於多數人使用預建立的 Promise,這個導覽會先講解回傳 Promise 的使用方式,之後再介紹如何建立。</p>
+
+<p>基本上,一個 Promise 是一個根據附加給他的 Callback 回傳的物件,以取代傳遞 Callback 到這個函數。</p>
+
+<p>舉例來說,下方的範例若用舊方式應該會有兩個 Callback,並根據成功或失敗來決定使用哪個:</p>
+
+<pre class="brush: js line-numbers language-js">function successCallback(result) {
+ console.log("It succeeded with " + result);
+}
+
+function failureCallback(error) {
+ console.log("It failed with " + error);
+}
+
+doSomething(successCallback, failureCallback);
+</pre>
+
+<p>而新作法會回傳一個 Promise,這樣你就可以附加 Callback:</p>
+
+<pre class="brush: js line-numbers language-js">let promise = doSomething();
+promise.then(successCallback, failureCallback);</pre>
+
+<p>再簡單點:</p>
+
+<pre class="brush: js line-numbers language-js">doSomething().then(successCallback, failureCallback);</pre>
+
+<p>我們稱之為 <em>非同步函數呼叫</em>。這個做法有許多好處,我們接下來看看。</p>
+
+<h2 id="保證">保證</h2>
+
+<p>不如舊做法,一個 Promise 有這些保證:</p>
+
+<ul>
+ <li>Callback 不會在<a href="/zh-TW/docs/Web/JavaScript/EventLoop#Run-to-completion">當次的迴圈運行結束</a>前呼叫。</li>
+ <li>Callback 用 .then 添加,在非同步運算結束<em>後</em>呼叫,像前面那樣。</li>
+ <li>複 Callback 可以透過重複呼叫 .then 達成。</li>
+</ul>
+
+<p>但 Promise 主要的立即好處是串連。</p>
+
+<h2 id="串連">串連</h2>
+
+<p>有個常見的需求是依序呼叫兩個以上的非同步函數,我們稱之為建立 <em>Promise 鏈</em>。</p>
+
+<p>看看魔術:<code>then</code> 函數回傳一個新的 Promise,不同於原本。</p>
+
+<pre class="brush: js">let promise = doSomething();
+let promise2 = promise.then(successCallback, failureCallback);
+</pre>
+
+<p>或</p>
+
+<pre class="brush: js">let promise2 = doSomething().then(successCallback, failureCallback);
+</pre>
+
+<p>第二個 Promise 不只代表 <code>doSomething()</code> 完成,還有<code>successCallback</code> 或 <code>failureCallback</code> ,這兩個非同步函數回傳另一個 Promise。如此一來,任何 Callback 附加給 <code>promise2</code> 會被排在 <code>successCallback</code> 或<code>failureCallback</code> 之後。</p>
+
+<p>基本上,每個 Promise 代表著鏈中另外一個非同步函數的完成。</p>
+
+<p>在古時候,多個非同步函數會使用 Callback 方式,導致波動拳問題:</p>
+
+<p><em>(原文 Pyramid of Doom 查無中文翻譯,以較常見之波動拳取代)</em></p>
+
+<pre class="brush: js">doSomething(function(result) {
+ doSomethingElse(result, function(newResult) {
+ doThirdThing(newResult, function(finalResult) {
+ console.log('Got the final result: ' + finalResult);
+ }, failureCallback);
+ }, failureCallback);
+}, failureCallback);
+</pre>
+
+<p>有了新方法,我們附加 Callback 到回傳的 Promise 上,來製造<em> Promise 鏈</em>:</p>
+
+<pre class="brush: js">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);
+</pre>
+
+<p><code>then</code> 的函數是選用的,以及 <code>catch(failureCallback)</code> 是 <code>then(null, failureCallback)</code> 的簡寫。你也許會想用<a href="/zh-TW/docs/Web/JavaScript/Reference/Functions/Arrow_functions">箭頭函數</a>取代:</p>
+
+<pre class="brush: js">doSomething()
+.then(result =&gt; doSomethingElse(result))
+.then(newResult =&gt; doThirdThing(newResult))
+.then(finalResult =&gt; {
+ console.log(`Got the final result: ${finalResult}`);
+})
+.catch(failureCallback);
+</pre>
+
+<p><strong>注意:</strong>永遠要回傳結果,否則 Callback 不會獲得前一個 Promise 的結果。</p>
+
+<h3 id="獲錯後串接">獲錯後串接</h3>
+
+<p>失敗<em>後</em>的串接是可行的,也就是說 <code>catch</code> 會非常好用,即使鏈中出錯。看看這個範例:</p>
+
+<pre class="brush: js">new Promise((resolve, reject) =&gt; {
+ console.log('Initial');
+
+ resolve();
+})
+.then(() =&gt; {
+ throw new Error('Something failed');
+
+ console.log('Do this');
+})
+.catch(() =&gt; {
+ console.log('Do that');
+})
+.then(() =&gt; {
+ console.log('Do this whatever happened before');
+});
+</pre>
+
+<p>他會輸出:</p>
+
+<pre>Initial
+Do that
+Do this whatever happened before
+</pre>
+
+<p>注意「Do this」沒有被輸出,因為「Something failed」錯誤導致拒絕。</p>
+
+<h2 id="錯誤傳遞">錯誤傳遞</h2>
+
+<p>在波動拳狀況中,你可能會看到三次 <code>failureCallback</code> ,在 Promise 鏈中只需要在尾端使用一次:</p>
+
+<pre class="brush: js">doSomething()
+.then(result =&gt; doSomethingElse(result))
+.then(newResult =&gt; doThirdThing(newResult))
+.then(finalResult =&gt; console.log(`Got the final result: ${finalResult}`))
+.catch(failureCallback);
+</pre>
+
+<p>基本上,一個 Promise 鏈遇到錯誤時會往下尋找 Catch 處理器。這是經過模組化的非同步程式:</p>
+
+<pre class="brush: js">try {
+ let result = syncDoSomething();
+ let newResult = syncDoSomethingElse(result);
+ let finalResult = syncDoThirdThing(newResult);
+ console.log(`Got the final result: ${finalResult}`);
+} catch(error) {
+ failureCallback(error);
+}
+</pre>
+
+<p>在 ECMAScript 2017 中,在有 <a href="/zh-TW/docs/Web/JavaScript/Reference/Statements/async_function"><code>async</code>/<code>await</code></a> 語法糖的同步程式達到高峰:</p>
+
+<pre class="brush: js">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);
+ }
+}
+</pre>
+
+<p>這基於 Promise,例如 <code>doSomething()</code>和之前一樣。你可以閱讀在<a href="https://developers.google.com/web/fundamentals/getting-started/primers/async-functions">這裡</a>閱讀更多。</p>
+
+<p>Promise 藉由捕捉所有錯誤,包含例外和程式錯誤,解決了 Callback 地獄的缺點。這是非同步運算的基本特性。</p>
+
+<h2 id="在舊有_API_上建立_Promise">在舊有 API 上建立 Promise</h2>
+
+<p>{{jsxref("Promise")}} 可以透過建構子建立。所以用建構子包裹舊的 API即可。</p>
+
+<p>在理想情況,所有非同步函數都會回傳 Promise,然而許多 API 仍然用舊的方式來傳遞成功、失敗 Callback,有個典型的例子是{{domxref("WindowTimers.setTimeout", "setTimeout()")}} :</p>
+
+<pre class="brush: js">setTimeout(() =&gt; saySomething("10 seconds passed"), 10000);
+</pre>
+
+<p>混合古代 Callback 和 Promise 是有問題的。如果 <code>saySomething</code> 失敗或有程式錯誤,那不會有任何錯誤被捕捉。</p>
+
+<p>幸運地,我們可以用 Promise 包裹他,最好盡可能的在最底層包裹,並永遠不要再直接呼叫他們:</p>
+
+<pre class="brush: js">const wait = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));
+
+wait(10000).then(() =&gt; saySomething("10 seconds")).catch(failureCallback);
+</pre>
+
+<p>基本上,Promise 建構子需要一個運作函數來正規地處理或拒絕 Promise。但因為 <code>setTimeout</code> 不會失敗,所以我們捨棄 reject。</p>
+
+<h2 id="組合">組合</h2>
+
+<p>{{jsxref("Promise.resolve()")}} 和 {{jsxref("Promise.reject()")}} 是用來正規地建立已經處理或拒絕的 Promise。他們在某些情況特別有用。</p>
+
+<p>{{jsxref("Promise.all()")}} 和 {{jsxref("Promise.race()")}} 是兩個組合工具用於使 Promise 平行運作。</p>
+
+<p>連續關聯是可行的,這是極簡 JavaScript 範例:</p>
+
+<pre class="brush: js">[func1, func2].reduce((p, f) =&gt; p.then(f), Promise.resolve());
+</pre>
+
+<p>基本上,我們摺疊(Reduce)一個非同步函數陣列成一個 Promise 鏈:<code>Promise.resolve().then(func1).then(func2);</code></p>
+
+<p>這可以用可重用的構成函數完成,通常用函數式編程:</p>
+
+<pre class="brush: js">let applyAsync = (acc,val) =&gt; acc.then(val);
+let composeAsync = (...funcs) =&gt; x =&gt; funcs.reduce(applyAsync, Promise.resolve(x));</pre>
+
+<p><code>composeAsync</code> 接受任何數量的函數作為參數,並回傳一個接受一個初始值用來傳給組合的新函數。這個好處是無論其中函數是非同步或否,都會保證用正確的順序執行:</p>
+
+<pre class="brush: js">let transformData = composeAsync(func1, asyncFunc1, asyncFunc2, func2);
+transformData(data);
+</pre>
+
+<p>ECMAScript 2017 中連續組合利用 async/await 更簡單:</p>
+
+<pre class="brush: js">for (let f of [func1, func2]) {
+ await f();
+}
+</pre>
+
+<h2 id="計時">計時</h2>
+
+<p>為了避免意外,傳給 <code>then</code> 的函數不會被同步地呼叫,即使是完成的 Promise:</p>
+
+<pre class="brush: js">Promise.resolve().then(() =&gt; console.log(2));
+console.log(1); // 1, 2
+</pre>
+
+<p>被傳入的函數會被放在子任務佇列而非立即執行,因此他會在當前的事件迴圈結束、佇列清空時執行,例如:</p>
+
+<pre class="brush: js">const wait = ms =&gt; new Promise(resolve =&gt; setTimeout(resolve, ms));
+
+wait().then(() =&gt; console.log(4));
+Promise.resolve().then(() =&gt; console.log(2)).then(() =&gt; console.log(3));
+console.log(1); // 1, 2, 3, 4
+</pre>
+
+<h2 id="看更多">看更多</h2>
+
+<ul>
+ <li>{{jsxref("Promise.then()")}}</li>
+ <li><a href="http://promisesaplus.com/">Promises/A+ 特色</a></li>
+ <li><a href="https://medium.com/@ramsunvtech/promises-of-promise-part-1-53f769245a53">Venkatraman.R - JS Promise (Part 1, Basics)</a></li>
+ <li><a href="https://medium.com/@ramsunvtech/js-promise-part-2-q-js-when-js-and-rsvp-js-af596232525c#.dzlqh6ski">Venkatraman.R - JS Promise (Part 2 - Using Q.js, When.js and RSVP.js)</a></li>
+ <li><a href="https://tech.io/playgrounds/11107/tools-for-promises-unittesting/introduction">Venkatraman.R - Tools for Promises Unit Testing</a></li>
+ <li><a href="http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html">Nolan Lawson: We have a problem with promises — Common mistakes with promises</a></li>
+</ul>