JavaScript 的 Async/Await 完勝 Promise 的六個理由
提醒一下各位,Node 現在從版本 7.6 開始就支援 async/await 了。如果你還沒有試過它,這裡有一堆帶有示例的理由來說明為什麼你應該馬上採用它,並且再也不會回頭。
[編者按]:貌似嵌入 gist 上的程式碼在 medium 原生 app 中不行,但是在移動瀏覽器上可以。如果你是在 app 中讀本文,請點選共享圖示,選擇“在瀏覽器中開啟”,才看得到程式碼片段。
Async/await 101
對於那些從未聽說過這個話題的人來說,如下是一個簡單的介紹:
- Async/await 是一種編寫非同步程式碼的新方法。之前非同步程式碼的方案是回撥和 promise。
- Async/await 實際上是建立在 promise 的基礎上。它不能與普通回撥或者 node 回撥一起用。
- Async/await 像 promise 一樣,也是非阻塞的。
- Async/await 讓非同步程式碼看起來、表現起來更像同步程式碼。這正是其威力所在。
語法
假設函式 getJSON
返回一個promise
,而該promise
的完成值是一些JSON物件。我們只想呼叫它,並輸出該JSON,然後返回"done"
。
如下是用 promise 實現的程式碼:
const makeRequest = () => getJSON() .then(data => { console.log(data) return "done" }) makeRequest()
而這就是用async/await
看起來的樣子:
const makeRequest = async () => { console.log(await getJSON()) return "done" } makeRequest()
這裡有一些區別:
1.函式前面有一個關鍵字 async
。await
關鍵字只用在用 async
定義的函式內。所有 async
函式都會隱式返回一個 promise,而 promise 的完成值將是函式的返回值(本例中是 "done"
)。
2.上面一點暗示我們不能在程式碼的頂層用 await
,因為這樣就不是在 async
函式內。
// 這段程式碼在頂層不能執行 // await makeRequest() // 這段程式碼可以執行 makeRequest().then((result) => { // do something })
3.await getJSON()
意味著 console.log
呼叫會一直等待,直到 getJSON()
promise 完成並列印出它的值。
為什麼 Async/await 更好?
1. 簡潔乾淨
看看我們少寫了多少程式碼!即使在上面那個人為的示例中,很顯然我們也是節省了不少程式碼。我們不必寫 .then
,建立一個匿名函式來處理響應,或者給不需要用的變數一個名稱 data
。我們還避免了程式碼巢狀。這些小小的優勢會快速累積起來,在後面的程式碼中會變得更明顯。
2. 錯誤處理
Async/await 會最終讓我們用同樣的結構( try/catch
)處理同步和非同步程式碼變成可能。在下面使用 promise 的示例中,如果 JSON.parse
失敗的話,try/catch
就不會處理,因為它是發生在一個 prmoise 中。我們需要在 promise 上呼叫 .catch
,並且重複錯誤處理程式碼。這種錯誤處理程式碼會比可用於生產的程式碼中的 console.log
更復雜。
const makeRequest = () => { try { getJSON() .then(result => { // this parse may fail const data = JSON.parse(result) console.log(data) }) // uncomment this block to handle asynchronous errors // .catch((err) => { // console.log(err) // }) } catch (err) { console.log(err) } }
現在看看用 async/await 實現的程式碼。現在 catch
塊會處理解析錯誤。
const makeRequest = async () => { try { // 這個解析會失敗 const data = JSON.parse(await getJSON()) console.log(data) } catch (err) { console.log(err) } }
3. 條件句
假設想做像下面的程式碼一樣的事情,獲取一些資料,並決定是否應該返回該資料,或者根據資料中的某些值獲取更多的細節。
const makeRequest = () => { return getJSON() .then(data => { if (data.needsAnotherRequest) { return makeAnotherRequest(data) .then(moreData => { console.log(moreData) return moreData }) } else { console.log(data) return data } }) }
這些程式碼看著就讓人頭疼。它只需將最終結果傳播到主 promise,卻很容易讓我們迷失在巢狀( 6 層)、大括號和返回語句中。
把這個示例用async / await 重寫,就變得更易於閱讀。
onst makeRequest = async () => { const data = await getJSON() if (data.needsAnotherRequest) { const moreData = await makeAnotherRequest(data); console.log(moreData) return moreData } else { console.log(data) return data } }
4. 中間值
你可能發現自己處於一種狀態,即呼叫你 promise1,然後用它的返回值來呼叫promise2,然後使用這兩個 promise 的結果來呼叫 promise3。你的程式碼很可能看起來像這樣:
const makeRequest = () => { return promise1() .then(value1 => { // do something return promise2(value1) .then(value2 => { // do something return promise3(value1, value2) }) }) }
如果 promise3
不需要 value1
,那麼很容易就可以把 promise 巢狀變扁平一點。如果你是那種無法忍受的人,那麼可能就會像下面這樣,在一個 Promise.all
中包含值 1 和 2,並避免更深層次的巢狀:
onst makeRequest = () => { return promise1() .then(value1 => { // do something return Promise.all([value1, promise2(value1)]) }) .then(([value1, value2]) => { // do something return promise3(value1, value2) }) }
這種方法為了可讀性而犧牲了語義。除了為了避免 promise 巢狀,沒有理由將 value1
和value2
併入一個陣列。
不過用 async/await 的話,同樣的邏輯就變得超級簡單直觀了。這會讓你對你拼命讓 promise 看起來不那麼可怕的時候所做過的所有事情感到懷疑。
const makeRequest = async () => { const value1 = await promise1() const value2 = await promise2(value1) return promise3(value1, value2) }
5. 錯誤棧
假如有一段鏈式呼叫多個 promise 的程式碼,在鏈的某個地方丟擲一個錯誤。
const makeRequest = () => { return callAPromise() .then(() => callAPromise()) .then(() => callAPromise()) .then(() => callAPromise()) .then(() => callAPromise()) .then(() => { throw new Error("oops"); }) } makeRequest() .catch(err => { console.log(err); // output // Error: oops at callAPromise.then.then.then.then.then (index.js:8:13) })
從 promise 鏈返回的錯誤棧沒有發現錯誤發生在哪裡的線索。更糟糕的是,這是誤導的;它包含的唯一的函式名是callAPromise
,它完全與此錯誤無關(不過檔案和行號仍然有用)。
但是,來自async / await的錯誤棧會指向包含錯誤的函式:
const makeRequest = async () => { await callAPromise() await callAPromise() await callAPromise() await callAPromise() await callAPromise() throw new Error("oops"); } makeRequest() .catch(err => { console.log(err); // output // Error: oops at makeRequest (index.js:7:9) })
當在本地環境中開發並在編輯器中開啟檔案時,這不是啥大事,但是當想搞清楚來自生產伺服器的錯誤日誌時,就相當有用了。在這種情況下,知道錯誤發生在makeRequest
中比知道錯誤來自一個又一個的 then
要好。
6. 除錯
最後但是同樣重要的是,在使用 async/await 時,一個殺手級優勢是除錯更容易。除錯 promise 一直是如此痛苦,有兩個原因:
1.沒法在返回表示式(無函式體)的箭頭函式中設定斷點。
試著在此處設定斷點
2.如果在.then
塊中設定斷點,並使用像單步除錯這類除錯快捷方式,偵錯程式不會移動到後面的 .then
,因為它只單步除錯同步程式碼。
有了 async/await,我們就不再需要那麼多箭頭函式,您可以像正常的同步呼叫一樣單步除錯 await 呼叫。
總結
Async/await 是過去幾年中新增到 JavaScript 中的最具革命性的功能之一。它讓我們意識到 promise 的語法有多混亂,並提供了直觀的替代。
相關文章
- Async/Await替代Promise的6個理由AIPromise
- ES6 Async/Await 完爆Promise的6個原因AIPromise
- [Javascript] Promise question with async awaitJavaScriptPromiseAI
- async/await 和 promise/promise.all 的示例AIPromise
- Promise && async/await的理解和用法PromiseAI
- 理解JavaScript的async/awaitJavaScriptAI
- 理解 JavaScript 的 async/awaitJavaScriptAI
- [完結篇] - 理解非同步之美 --- promise與async await (三)非同步PromiseAI
- Promise, Generator, async/await的漸進理解PromiseAI
- 20分鐘帶你掌握JavaScript Promise和 Async/AwaitJavaScriptPromiseAI
- 1 分鐘讀完《10 分鐘學會 JavaScript 的 Async/Await》JavaScriptAI
- Async/Await 代替 Promise.all()AIPromise
- Promise與async/await與GeneratorPromiseAI
- Promise和async await詳解PromiseAI
- Promise/async/await 研究筆記PromiseAI筆記
- 【譯】JavaScript中的async/awaitJavaScriptAI
- JavaScript 的 async/await 理解(4)JavaScriptAI
- 與Promise血脈相連的async/awaitPromiseAI
- ES6 Promise 和 Async/await的使用PromiseAI
- 重構:從Promise到Async/AwaitPromiseAI
- 單例模式,promise與async/await單例模式PromiseAI
- JavaScript中的async/await詳解JavaScriptAI
- JavaScript:async/await的基礎用法JavaScriptAI
- JavaScript Promises, async/awaitJavaScriptPromiseAI
- JavaScript async await 使用JavaScriptAI
- 深入理解javascript系列(十九):從Promise開始到async/awaitJavaScriptPromiseAI
- 細說 async/await 相較於 Promise 的優勢AIPromise
- 面試向:Async/Await 代替 Promise.all()面試AIPromise
- promise以及async、await學習總結PromiseAI
- 理解koa2 之 async + await + promiseAIPromise
- Javascript | 分別用async await非同步方法和Promise來實現一個簡易的求職程式JavaScriptAI非同步Promise求職
- [譯]JavaScript: Promises 介紹及為何 Async/Await 最終取得勝利JavaScriptPromiseAI
- async await函式效能與Promise併發AI函式Promise
- async await、Promise、setTimeout執行順序AIPromise
- 學習JavaScript迴圈下的async/awaitJavaScriptAI
- [譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async IteratorsJavaScriptSymbolAI
- [譯文] JavaScript async 和 awaitJavaScriptAI
- promise、async和await之執行順序的那點事PromiseAI