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()
這里有一些區別:
- 函數前面有一個關鍵字 async 。 await 關鍵字只用在用 async 定義的函數內。所有 async 函數都會隱式返回一個 promise,而 promise 的完成值將是函數的返回值(本例中是 "done" )。
- 上面一點暗示我們不能在代碼的頂層用 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 一直是如此痛苦,有兩個原因:
- 沒法在返回表達式(無函數體)的箭頭函數中設置斷點。
試著在此處設置斷點
2.如果在 .then 塊中設置斷點,并使用像單步調試這類調試快捷方式,調試器不會移動到后面的 .then ,因為它只單步調試同步代碼。
有了 async/await,我們就不再需要那么多箭頭函數,您可以像正常的同步調用一樣單步調試 await 調用。
總結
Async/await 是過去幾年中添加到 JavaScript 中的最具革命性的功能之一。它讓我們意識到 promise 的語法有多混亂,并提供了直觀的替代。
關注
您可能對使用此功能有一些正當的懷疑:
- 它讓異步代碼變得不那么明顯:我們的眼睛已經學會了只要看到回調或者 .then 就認出是異步代碼,還需要花上幾周的時間才能適應新的標志。不過 C#已經有這個功能多年了,熟悉它的人都知道這是這種小的、暫時的不便是值得的。
- Node 7 現在還不是 LTS 版本:是的,不過 Node 8 下個月就會出來,而將我們的代碼庫遷移到新版本很有可能不費吹灰之力。
- JavaScript
- ES6
- Asynchronous
- Nodejs
- Programming
來自:http://www.w3ctech.com/topic/2021