一文帶你了解async/await的使用
開篇觀點(diǎn),async/await
不僅僅是 Promise 上面的語法糖,因?yàn)?async/await
確實(shí)提供了切實(shí)的好處。
async/await
讓異步代碼變成同步的方式,從而使代碼更具表現(xiàn)力和可讀性。async/await
統(tǒng)一了異步編程的經(jīng)驗(yàn);以及提供了更好的錯(cuò)誤堆棧跟蹤。
關(guān)于 JS 中異步編程的一點(diǎn)歷史
異步編程在 JavaScript 中很常見。每當(dāng)我們需要進(jìn)行網(wǎng)絡(luò)服務(wù)調(diào)用、文件訪問或數(shù)據(jù)庫操作時(shí),盡管語言是單線程的,但異步性是我們防止用戶界面被阻塞的方法。
在 ES6 之前,回調(diào)是猿們處理異步編程的方式。我們表達(dá)時(shí)間依賴性(即異步操作的執(zhí)行順序)的唯一方法是將一個(gè)回調(diào)嵌套在另一個(gè)回調(diào)中,這導(dǎo)致了所謂的回調(diào)地獄。
Es6 中引入了 Promise
,它是一個(gè)用于異步操作的一流對(duì)象,我們可以輕松地傳遞、組合、聚合和應(yīng)用轉(zhuǎn)換。時(shí)間上的依賴性通過 then
方法鏈干凈地表達(dá)出來。
有了 Promise
這個(gè)強(qiáng)大的伙伴,聽起來異步編程在 JS 中是一個(gè)已經(jīng)解決的問題,對(duì)嗎?
恩,還沒有,因?yàn)橛袝r(shí)候 Promise 的級(jí)別太低了,不太適合使用。
有時(shí) Promise 的級(jí)別太低,不適合使用
盡管出現(xiàn)了 Promise,但在 JS 中仍然需要一個(gè)更高級(jí)別的語言結(jié)構(gòu)來進(jìn)行異步編程。
我們來看個(gè)例子, 假設(shè)我們需要某個(gè)函數(shù)在某個(gè)時(shí)間間隔輪詢一個(gè)API。當(dāng)達(dá)到最大重試次數(shù)時(shí),它就會(huì)解析為 null
。
下面是 Promise 的一種解決方案:
let count = 0; function apiCall() { return new Promise((resolve) => // a在第6次重試時(shí),它被解析為 "value"。 count++ === 5 ? resolve('value') : resolve(null) ); } function sleep(interval) { return new Promise((resolve) => setTimeout(resolve, interval)); } function poll(retry, interval) { return new Promise((resolve) => { // 為了簡潔起見,跳過錯(cuò)誤處理 if (retry === 0) resolve(null); apiCall().then((val) => { if (val !== null) resolve(val); else { sleep(interval).then(() => { resolve(poll(retry - 1, interval)); }); } }); }); } poll(6, 1000).then(console.log); // 'value'
這種解決方案的直觀性和可讀性取決于人們對(duì)Promise的熟悉程度,以及 Promise.resolve
如何 "平鋪" Promise 和遞歸。對(duì)我來說,這不是寫這樣一個(gè)函數(shù)的最可讀的方式。
使用 async/await
我們用 async/await
語法重寫上述解決方案:
async function poll(retry, interval) { while (retry >= 0) { const value = await apiCall().catch((e) => {}); if (value !== null) return value; await sleep(interval); retry--; } return null; }
我想大多數(shù)人都會(huì)覺得上面的解決方案更有可讀性,因?yàn)槲覀兡軌蚴褂盟姓5恼Z言結(jié)構(gòu),如循環(huán)、異步操作的 try-catch
等。
這可能是 async/await
的最大賣點(diǎn)--使我們能夠以同步的方式編寫異步代碼。另一方面,這可能是對(duì) async/await
最常見的反對(duì)意見的來源,稍后再談這個(gè)問題。
順便說一下,await
甚至有正確的操作符優(yōu)先級(jí),所以await a + await b
等于(await a) + (await b)
,而不是讓我們說await (a + await b)
。
async/await 在同步和異步代碼中提供了統(tǒng)一的體驗(yàn)
async/await
的另一個(gè)好處是,await
自動(dòng)將任何非Promise(non-thenables)包裝成 Promises 。await
的語義等同于Promise.resolve
,這意味著可以 await
任何東西:
function fetchValue() { return 1; } async function fn() { const val = await fetchValue(); console.log(val); // 1 } // 上面等同于下面 function fn() { Promise.resolve(fetchValue()).then((val) => { console.log(val); // 1 }); }
如果我們將 then
方法附加到從 fetchValue
返回的數(shù)字 1
上,就會(huì)出現(xiàn)以下錯(cuò)誤。
function fetchValue() { return 1; } function fn() { fetchValue().then((val) => { console.log(val); }); } fn(); // ? Uncaught TypeError: fetchValue(...).then is not a function
最后, 從 async
函數(shù)返回的任何東西都是一個(gè) Promise:
Object.prototype.toString.call((async function () {})()); // '[object Promise]'
async/await 提供更好的錯(cuò)誤堆棧跟蹤
V8工程師Mathias寫了一篇名為Asynchronous stack traces: why await beats Promise#then()
的文章,介紹了為什么與 Promise相比,引擎更容易捕捉和存儲(chǔ) async/await
的堆棧跟蹤。事例如下:
async function foo() { await bar(); return 'value'; } function bar() { throw new Error('BEEP BEEP'); } foo().catch((error) => console.log(error.stack)); // Error: BEEP BEEP // at bar (<anonymous>:7:9) // at foo (<anonymous>:2:9) // at <anonymous>:10:1
async 版本正確地捕獲了錯(cuò)誤堆棧跟蹤。
我們?cè)賮砜纯?Promise
版本。
function foo() { return bar().then(() => 'value'); } function bar() { return Promise.resolve().then(() => { throw new Error('BEEP BEEP'); }); } foo().catch((error) => console.log(error.stack)); // Error: BEEP BEEP at <anonymous>:7:11
堆棧跟蹤丟失。從匿名的箭頭函數(shù)切換到命名的函數(shù)聲明有一點(diǎn)幫助,但幫助不大:
function foo() { return bar().then(() => 'value'); } function bar() { return Promise.resolve().then(function thisWillThrow() { throw new Error('BEEP BEEP'); }); } foo().catch((error) => console.log(error.stack)); // Error: BEEP BEEP // at thisWillThrow (<anonymous>:7:11)
對(duì) async/await 常見反對(duì)意見
對(duì) async/await
主要有兩種常見的反對(duì)意見。
首先,當(dāng)獨(dú)立的異步函數(shù)調(diào)用可以用Promise.all
并發(fā)處理時(shí),如果我們還大量使用async/await
可能會(huì)導(dǎo)致濫用,這樣會(huì)造成開發(fā)者不去試圖了解 Promise 的幕后是如何工作,而只是一味的使用 async/await
。
第二種情況更為微妙。一些函數(shù)式編程愛好者認(rèn)為 async/await
會(huì)招致命令式編程。從 FP 程序員的角度來看,能夠使用循環(huán)和 try catch
并不是一件好事,因?yàn)檫@些語言結(jié)構(gòu)意味著副作用,并鼓勵(lì)使用不那么理想的錯(cuò)誤處理。
我對(duì)這種說法待保留意見。FP程序員理所當(dāng)然地關(guān)心他們程序中的確定性。他們希望對(duì)自己的代碼有絕對(duì)的信心。為了達(dá)到這個(gè)目的,需要一個(gè)復(fù)雜的類型系統(tǒng),其中包括Result等類型。但我不認(rèn)為async/await
本身與FP不相容。
無論如何,對(duì)于大多數(shù)人來說,包括我在內(nèi),F(xiàn)P仍然是一種后天的味道(盡管我確實(shí)認(rèn)為FP超級(jí)酷,而且我正在慢慢學(xué)習(xí)它)。async/await
提供的正??刂屏髡Z句和try catch
錯(cuò)誤處理,對(duì)于我們?cè)?JavaScript 中協(xié)調(diào)復(fù)雜的異步操作是非常寶貴的。這正是為什么說 "async/await只是一種語法糖" 是一種輕描淡寫的說法。
以上就是一文帶你了解async/await的使用的詳細(xì)內(nèi)容,更多關(guān)于async/await的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Javascript設(shè)置對(duì)象的ReadOnly屬性(示例代碼)
本篇文章主要介紹了Javascript設(shè)置對(duì)象的ReadOnly屬性(示例代碼) 需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-12-12javascript作用域鏈與執(zhí)行環(huán)境詳解
這篇文章主要為大家詳細(xì)介紹了javascript作用域鏈與執(zhí)行環(huán)境,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03070823更新的一個(gè)[消息提示框]組件 兼容ie7
070823更新的一個(gè)[消息提示框]組件 兼容ie7...2007-08-08js/jQuery簡單實(shí)現(xiàn)選項(xiàng)卡功能
本篇文章主要是對(duì)js/jQuery簡單實(shí)現(xiàn)選項(xiàng)卡功能的示例代碼進(jìn)行了介紹,需要的朋友可以過來參考下,希望對(duì)大家有所幫助2014-01-01Omi v1.0.2發(fā)布正式支持傳遞javascript表達(dá)式
這篇文章主要介紹了Omi v1.0.2發(fā)布正式支持傳遞javascript表達(dá)式,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03純JS實(shí)現(xiàn)可用于頁碼更換的飛頁特效示例
這篇文章主要介紹了純JS實(shí)現(xiàn)可用于頁碼更換的飛頁特效,涉及javascript結(jié)合時(shí)間函數(shù)的數(shù)學(xué)運(yùn)算與頁面元素屬性動(dòng)態(tài)修改相關(guān)操作技巧,需要的朋友可以參考下2018-05-05