JavaScript:async/await的基礎用法
相對於回撥函式來說,Promise
是一種相對優雅的選擇。那麼有沒有更好的方案呢?答案就是async/await
。
優勢主要體現在,級聯呼叫,也就是幾個呼叫依次發生的場景。
async/await
。被稱為到目前最優雅的非同步過程解決方案,不知道你是否認同,反正我是信了。
相對於Promise
,async/await
有什麼優點?
比較場景: 級聯呼叫,也就是幾個呼叫依次發生的場景
Promise
主要用then
函式的鏈式呼叫,一直點點點,是一種從左向右的橫向寫法。
async/await
從上到下,順序執行,就像寫同步程式碼一樣。這更符合人編寫程式碼的習慣Promise
的then
函式只能傳遞一個引數,雖然可以通過包裝成物件,但是這會導致傳遞冗餘資訊,頻繁的解析又重新組合引數,比較麻煩。
async/await
沒有這個限制,就當做普通的區域性變數來處理好了,用let
或者const
定義的塊級變數,想怎麼用就怎麼用,想定義幾個就定義幾個,完全沒有限制,也沒有冗餘的工作。Promise
在使用的時候最好將同步程式碼和非同步程式碼放在不同的then
節點中,這樣結構更加清晰。
async/await
整個書寫習慣都是同步的,不需要糾結同步和非同步的區別。當然,非同步過程需要包裝成一個Promise
物件,放在await
關鍵字後面,這點還是要牢記的。Promise
是根據函數語言程式設計的正規化,對非同步過程進行了一層封裝。
async/await
是基於協程的機制,是真正的“儲存上下文,控制權切換 … … 控制權恢復,取回上下文”這種機制,是對非同步過程更精確的一種描述。
程式、執行緒和協程的理解
上面的文章很好地解釋了這幾個概念的區別。
如果不糾結細節,可以簡單地認為:程式 > 執行緒 > 協程;
協程可以獨立完成一些與介面無關的工作,不會阻塞主執行緒渲染介面,也就是不會卡。
協程,雖然小一點,不過能完成我們程式設計師交給的任務。而且我們可以自由控制執行和阻塞狀態,不需要求助於高大上的系統排程,這才是重點。
async/await
是基於Promise
的,是進一步的一種優化。不過再寫程式碼的時候,Promise
本身的API
出現得很少,很接近同步程式碼的寫法。
await
關鍵字使用時有哪些注意點?
- 只能放在
async
函式內部使用,不能放在普通函式裡面,否則會報錯。 - 後面放
Promise
物件,在Pending
狀態時,相應的協程會交出控制權,進入等待狀態。這個是本質。 await
是async wait
的意思,wait
的是resolve(data)
訊息,並把資料data
返回。比如,下面程式碼中,當Promise
物件由Pending
變為Resolved
的時候,變數a
就等於data
;然後再順序執行下面的語句console.log(a);
這真的是等待,真的是順序執行,表現和同步程式碼幾乎一模一樣。
const a = await new Promise((resolve, reject) => {
// async process ...
return resolve(data);
});
console.log(a);
await
後面也可以跟同步程式碼,不過系統會自動轉化成一個Promise
物件。
比如
const a = await 'hello world';
其實就相當於
const a = await Promise.resolve('hello world');
這跟同步程式碼
const a = 'hello world';
是一樣的,還不如省點事,去掉這裡的await關鍵字。
await
只關心非同步過程成功的訊息resolve(data)
,拿到相應的資料data
。至於失敗訊息reject(error)
,不關心,不處理。
當然對於錯誤訊息的處理,有以下幾種方法供選擇:
(1)讓await
後面的Promise
物件自己catch
(2)也可以讓外面的async
函式返回的Promise
物件統一catch
(3)像同步程式碼一樣,放在一個try...catch
結構中
async
關鍵字使用時有哪些注意點?
- 有了這個
async
關鍵字,只是表明裡面可能有非同步過程,裡面可以有await
關鍵字。當然,全部是同步程式碼也沒關係。當然,這時候這個async
關鍵字就顯得多餘了。不是不能加,而是不應該加。 async
函式,如果裡面有非同步過程,會等待;
但是async
函式本身會馬上返回,不會阻塞當前執行緒。
可以簡單認為,
async
函式工作在主執行緒,同步執行,不會阻塞介面渲染。
async
函式內部由async
關鍵字修飾的非同步過程,工作在相應的協程上,會阻塞等待非同步任務的完成再返回。
async
函式的返回值是一個Promise
物件,這個是和普通函式本質不同的地方。這也是使用時重點注意的地方
(1)return newPromise();
這個符合async
函式本意;
(2)return data;
這個是同步函式的寫法,這裡是要特別注意的。這個時候,其實就相當於Promise.resolve(data);
還是一個Promise
物件。
在呼叫async
函式的地方通過簡單的=
是拿不到這個data
的。
那麼怎麼樣拿到這個data
呢?
很簡單,返回值是一個Promise
物件,用.then(data => { })
函式就可以。
(3)如果沒有返回,相當於返回了Promise.resolve(undefined);
await
是不管非同步過程的reject(error)
訊息的,async
函式返回的這個Promise
物件的catch
函式就負責統一抓取內部所有非同步過程的錯誤。
async
函式內部只要有一個非同步過程發生錯誤,整個執行過程就中斷,這個返回的Promise
物件的catch
就能抓到這個錯誤。async
函式執行和普通函式一樣,函式名帶個()
就可以了,引數個數隨意,沒有限制;也需要有async
關鍵字。
只是返回值是一個Promise
物件,可以用then
函式得到返回值,用catch
抓去整個流程中發生的錯誤。
基本套路
Step1:用Promise
物件包裝非同步過程,這個和Promise
的使用一樣。只是引數個數隨意,沒有限制。
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve('sleep for ' + ms + ' ms');
}, ms);
});
}
Step2:定義非同步流程,可以將按照需要定製,就像寫同步程式碼那樣
async function asyncFunction() {
console.time('asyncFunction total executing:');
const sleep1 = await sleep(2000);
console.log('sleep1: ' + sleep1);
const [sleep2, sleep3, sleep4]= await Promise.all([sleep(2000), sleep(1000), sleep(1500)]);
console.log('sleep2: ' + sleep2);
console.log('sleep3: ' + sleep3);
console.log('sleep4: ' + sleep4);
const sleepRace = await Promise.race([sleep(3000), sleep(1000), sleep(1000)]);
console.log('sleep race: ' + sleepRace);
console.timeEnd('asyncFunction total executing:');
return 'asyncFunction done.' // 這個可以不返回,這裡只是做個標記,為了顯示流程
}
Step3:像普通函式呼叫async
函式,在then函式中獲取整個流程的返回資訊,在catch
函式統一處理出錯資訊
asyncFunction().then(data => {
console.log(data); // asyncFunction return 的內容在這裡獲取
}).catch(error => {
console.log(error); // asyncFunction 的錯誤統一在這裡抓取
});
console.log('after asyncFunction code executing....'); // 這個代表asyncFunction函式後的程式碼,
// 顯示asyncFunction本身會立即返回,不會阻塞主執行緒
流程解析
上面的程式碼執行之後,輸出的log
如下,顯示了程式碼執行流程
after asyncFunction code executing....
sleep1: sleep for 2000 ms
sleep2: sleep for 2000 ms
sleep3: sleep for 1000 ms
sleep4: sleep for 1500 ms
sleep race: sleep for 1000 ms
asyncFunction total executing:: 5006.276123046875ms
asyncFunction done.
after asyncFunction code executing....
程式碼位置在async
函式asyncFunction()
呼叫之後,反而先輸出。這說明async
函式asyncFunction()
呼叫之後會馬上返回,不會阻塞主執行緒。sleep1: sleep for 2000 ms
這是第一個await
之後的第一個非同步過程,最先執行,也最先完成,說明後面的程式碼,不論是同步和非同步,都在等他執行完畢。sleep2 ~ sleep4
這是第二個await
之後的Promise.all()
非同步過程。這是“比慢模式”,三個sleep
都完成後,再執行下面的程式碼,耗時最長的是2000ms
;sleep race: sleep for 1000 ms
這是第三個await
之後的Promise.race()
非同步過程。這是“比快模式”,耗時最短sleep
都完成後,就執行下面的程式碼。耗時最短的是1000ms
;asyncFunction total executing::5006.276123046875ms
這是最後的統計總共執行時間程式碼。三個await
之後的非同步過程之和1000(獨立的) + 2000(Promise.all) + 1000(Promise.race) = 5000ms
這個和統計出來的5006.276123046875ms
非常接近。說明上面的非同步過程,和同步程式碼執行過程一致,協程真的是在等待非同步過程執行完畢。asyncFunction done.
這個是async
函式返回的資訊,在執行時的then
函式中獲得,說明整個流程完畢之後引數傳遞的過程。
異常處理
async
標註過的函式,返回一個Promise
物件,採用.then().catch()
的方式來進行異常處理,是非常自然的方法,也推薦這麼做。就像上面的step3
那樣做。- 另外一種方法,就是對於非同步過程採用
await
關鍵字,採用同步的try{} catch(){}
的方式來進行異常處理。 - 這裡要注意的是
await
關鍵字只能用在async
標註的函式中,所以,原來的函式,不管以前是同步的還是非同步的,都要加上async
關鍵字,比如componentDidMount()
就要變為async componentDidMount()
才可以在內部使用await
關鍵字,不過功能上沒有任何影響。 - 另外,採用同步的
try{} catch(){}
的方式,可以把同步,非同步程式碼都可以放在裡面,有錯誤都能抓到,比如null.length
這種,也能抓到。
async componentDidMount() { // 這是React Native的回撥函式,加個async關鍵字,沒有任何影響,但是可以用await關鍵字
// 將非同步和同步的程式碼放在一個try..catch中,異常都能抓到
try {
let array = null;
let data = await asyncFunction(); // 這裡用await關鍵字,就能拿到結果值;否則,沒有await的話,只能拿到Promise物件
if (array.length > 0) { // 這裡會丟擲異常,下面的catch也能抓到
array.push(data);
}
} catch (error) {
alert(JSON.stringify(error))
}
}
這裡模擬的是網路過程。一般情況,
array
是一個陣列,用if (array.length > 0)
判斷一下長度,有值再處理,沒有問題。但是,一旦網路出問題,array
就是一個null
,平時工作很好的if (array.length > 0)
判斷就會拋異常,JS程式碼就中斷,停止工作,會帶來意想不到的問題。這裡加了一個
try..catch
結構,這種異常就能捕獲,(這是同步程式碼中的異常,不能用.then().catch()
抓到),根據異常資訊,一般是null
沒有length
屬性,方便定位問題。這裡的話用if (array && (array.length > 0))
就會安全一點。
參考文章
本文只是介紹了async/await
一種基礎的用法。一個例子,將三種Promise
使用中常用的場景模式都包括進去了,並且程式碼風格和同步程式碼非常相似。相比之下async/await
這套非同步程式碼程式設計方式確實比較優雅。
相關文章
- JavaScript基礎——深入學習async/awaitJavaScriptAI
- 理解JavaScript的async/awaitJavaScriptAI
- 【JS基礎】從JavaScript中的for...of說起(下) - async和awaitJSJavaScriptAI
- [譯]JavaScript async / await:好處、坑和正確用法JavaScriptAI
- JavaScript async await 使用JavaScriptAI
- JavaScript Promises, async/awaitJavaScriptPromiseAI
- Promise && async/await的理解和用法PromiseAI
- JavaScript 的 async/await 理解(4)JavaScriptAI
- 【譯】JavaScript中的async/awaitJavaScriptAI
- [Javascript] Promise question with async awaitJavaScriptPromiseAI
- JavaScript中的async/await詳解JavaScriptAI
- 關於C#中async/await的用法C#AI
- [譯文] JavaScript async 和 awaitJavaScriptAI
- [譯]JavaScript Symbols, Iterators, Generators, Async/Await, and Async IteratorsJavaScriptSymbolAI
- 學習JavaScript迴圈下的async/awaitJavaScriptAI
- vue中非同步函式async和await的用法Vue非同步函式AI
- JavaScript async和await 非同步操作JavaScriptAI非同步
- 深入理解 promise、generator+co、async/await 用法PromiseAI
- Async +AwaitAI
- Async/awaitAI
- 紅寶石async/await用法示例出錯了嗎?AI
- 004 Rust 非同步程式設計,async await 的詳細用法Rust非同步程式設計AI
- [譯] JavaScript - Generator-Yield/Next 和 Async-AwaitJavaScriptAI
- JavaScript ES6 async/await的簡單學習demoJavaScriptAI
- async和awaitAI
- 理解 async/awaitAI
- 理解 js的 async/awaitJSAI
- async和await的使用AI
- JavaScript中async和await的使用以及佇列問題JavaScriptAI佇列
- JavaScript非同步程式設計–Generator函式、async、awaitJavaScript非同步程式設計函式AI
- 20分鐘帶你掌握JavaScript Promise和 Async/AwaitJavaScriptPromiseAI
- 【譯】Async/Await(三)——Aysnc/Await模式AI模式
- async await詳解AI
- 淺談async/awaitAI
- JavaScript深入淺出非同步程式設計三、async、awaitJavaScript非同步程式設計AI
- async的基本用法
- Rust 標準庫中的 async/await (async-std)RustAI
- Vue中async await的使用示例VueAI