Async/Await 是什麼?
Async/Await 也就是大家知道的非同步函式,它是一個用來控制 JavaScript 非同步流程的一個記號。而在很多現代瀏覽器上也曾實現過這樣的設想。它的靈感來源於C# 和 F#,現在 Async/Await 在ES2017已經平穩著陸。
通常我們認為 async function
是一個能返回 Promise
的 function
。你也可以在 async function
使用 await
關鍵字。 await
關鍵字可以放在一個需要返回Promise的表示式前,所得到的值被從Promise裡面剝離開,以便能用更直觀的同步體驗。我們來看一下實際的程式碼更直觀。
// 這是一個簡單的返回 Promise 函式
// 功能是在兩秒以後 resolve("MESSAGE") .
function getMessage() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve("MESSAGE"), 2000);
});
}
複製程式碼
async function start() {
const message = await getMessage();
return `The message is: ${message}`;
}
複製程式碼
start().then(msg => console.log(msg));
// "The message is: MESSAGE"
複製程式碼
為什麼要用 Async/Await?
Async/Await 提供了一個看起來相對同步的方法來執行非同步程式碼。同時也提供了一種簡潔而直觀的方法來處理非同步的錯誤,因為它實現了try…catch
標記,這是JavaScript裡面最常見的一種同步模式。
在我們開始冒險之前,我們應該清楚,Async/Await 是建立在 JavaScript Promises 上的,而且關於它的知識是很重要的。
關於記號
Async 函式
要建立一個 async
函式,一般就要把 async
關鍵字放在宣告函式之前,就像這樣:
async function fetchWrapper() {
return fetch('/api/url/');
}
const fetchWrapper = async () => fetch('/api/url/');
複製程式碼
const obj = {
async fetchWrapper() {
// ...
}
}
複製程式碼
Await 關鍵字
async function updateBlogPost(postId, modifiedPost) {
const oldPost = await getPost(postId);
const updatedPost = { ...oldPost, ...modifiedPost };
const savedPost = await savePost(updatedPost);
return savedPost;
}
複製程式碼
在這裡的 await
是用在其他返回 promise 的函式前。在第一行,oldPost被賦值為getPost執行resolve後返回的value。在下一行,我們使用瞭解構賦值來演示怎樣把 oldPost 和 modifiedPost 合併。最終我們把 post 儲存下來,返回了 savedPost 的結果。
示例 / FAQ
?️“到底怎麼處理錯誤?”
這是一個好問題!當你使用 async/await 的時候,你也可以使用 try...catch
。在下面展示了,我們非同步的 fetch 了一些東西,返回了某種錯誤,我們可以在catch裡面拿到錯誤。
async function tryToFetch() {
try {
const response = await fetch('/api/data', options);
return response.json();
} catch(err) {
console.log(`An error occured: ${err}`);
// 比起返回一個錯誤
// 我們可以返回一個空的data
return { data: [] };
}
}
複製程式碼
tryToFetch().then(data => console.log(data));
複製程式碼
?️ ️“我還是不知道為什麼 async/await 比 callbacks/promises 好.”
很高興你問了這個問題,這裡有一個例子可以說明不同。我們這裡只是想要非同步的 fetch 一些資料,然後得到資料後,簡單的返回一些經過處理的data,如果有錯誤,我們簡單的只是想要返回一個物件。
// 我們這裡有 fetchSomeDataCB, 和 processSomeDataCB
// NOTE: CB 代表 callback
function doWork(callback) {
fetchSomeDataCB((err, fetchedData) => {
if(err) {
callback(null, [])
}
processSomeDataCB(fetchedData, (err2, processedData) => {
if(err2) {
callback(null, []);
}
// return the processedData outside of doWork
callback(null, processedData);
});
});
}
doWork((err, processedData) => console.log(processedData));
複製程式碼
// 我們這裡有 fetchSomeDataP, 和 processSomeDataP
// NOTE: P 意味著這個函式返回一個 Promise
function doWorkP() {
return fetchSomeDataP()
.then(fetchedData => processSomeDataP(fetchedData))
.catch(err => []);
}
doWorkP().then(processedData => console.log(processedData));
複製程式碼
async function doWork() {
try {
const fetchedData = await fetchSomeDataP();
return processSomeDataP(fetchedData);
} catch(err) {
return [];
}
}
doWork().then(processedData => console.log(processedData));
複製程式碼
Callback vs Promise vs Async/Await
?️“他的併發性如何”
當我們需要有順序的做一些事情,我們通常用await一個一個宣告所有的步驟。在這之前為了理解併發,我們必須使用Promise.all
。如果我們現在有三個非同步動作需要平行執行,在加上await之前,我們需要讓所有的Promise先開始。
// 這不是解決方法,他們會逐個執行
async function sequential() {
const output1 = await task1();
const output2 = await task2();
const output3 = await task3();
return combineEverything(output1, output2, output3);
}
複製程式碼
因為上述程式碼只是依次的執行了三個任務,而沒有併發的執行,後一個會依賴前一個執行完成。所以我們要改造成Promise.all
的方式。
// 這就可以併發的執行
async function parallel() {
const promises = [
task1(),
task2(),
task3(),
];
const [output1, output2, output 3] = await Promise.all(promises);
);
return combineEverything(output1, output2, output3);
}
複製程式碼
在這個例子上,我們首先執行了3個非同步的任務,之後把Promise都儲存進了一個array。我們使用 Promise.all
來完成來全部併發結果的收集。
另外的一些提示
- 你很容易會忘記每次你
await
一些程式碼, 你需要宣告這個函式是一個async function
。 - 當你使用
await
時候,它值暫停了所涉及的async function
。 換句話說,下面的程式碼會在其他東西log之前log'wanna race?'
。
const timeoutP = async (s) => new Promise((resolve, reject) => {
setTimeout(() => resolve(s*1000), s*1000)
});
複製程式碼
[1, 2, 3].forEach(async function(time) {
const ms = await timeoutP(time);
console.log(`This took ${ms} milliseconds`);
});
複製程式碼
console.log('wanna race?');
複製程式碼
當你的第一個await 的 promise在主執行緒上返回執行了結果,在forEach外面的log不會被阻塞。
瀏覽器支援
看一看這張瀏覽器支援表。
Node 支援
node 7.6.0
以及以上版本支援 Async/Await !
原文:Asynchronous Adventures in JavaScript: Async/Await
翻譯:Dominic Ming