[譯文] JavaScript async 和 await

zhangbao90s發表於2018-03-11

原文連結:davidwalsh.name/async-await

JavaScript Promise 是替代傳統回撥函式的一個方案,是回撥函式的一個改進版。但使用 Promise 會讓程式碼中大量出現 then 方法,一長串的那種。ES2017 引入了一種新的處理非同步任務的方式----async 函式,它比使用 Promise API 更加簡潔。

引入的 async 函式,使用了兩個新的關鍵字:async 和 await。

快速預覽

  • async 關鍵字在函式宣告前使用。
  • await 用於處理 Promise 物件。
  • await 只能用在 async 函式中。
  • async 函式總是返回一個 Promise 物件,不論函式是否 return Promise 物件。
  • async/await 和 Primose 物件在本質上是一樣的。

使用 async 和 await 的好處

  • 程式碼更加簡潔、精確。
  • 因為少回撥,Debug 起來更容易。
  • 從 Promise then/catch 書寫形式過渡過來非常自然。
  • 程式碼更加“自上而下”,少巢狀。

async 和 await 的基本使用

一例勝千言,先看一個簡單的使用 async 和 await 的例子。

// 將函式宣告為一個 async 函式,這樣就能在內部使用 await 了
async function fetchContent() {
	// 使用 await,而非 fetch.then
	let content = await fetch('/');
	let text = await content.text();

	// async 函式最終返回一個 resolved 狀態的 Promise 物件,
	// Promise 物件的 then 回撥方法接收的引數就是這裡的 text
	return text;
}

// 呼叫 async 函式
let promise = fetchContent.then(...);
複製程式碼

async 函式以 async 關鍵字標記,await 只能用在 async 函式中。await 後面緊跟的是生成 Promise 物件的(promise-yielding)操作,對應這裡的 fetch API。只有等到第一個 await 後面的操作完成後,才會繼續執行後面的程式碼。最終,async 函式返回一個 resolved 狀態的 Promise 物件,而這個 Promise 物件的 then 回撥方法中,接收的引數就是 text。

從 Promise 過渡到 async 函式

讓我們看一下,怎麼將一個 Promise 例子改寫成 async 函式的形式。

// 之前:回撥城!
fetch('/users.json')
	.then(response => response.json())
	.then(json => {
		console.log(json);
	})
	.catch(err = {
		console.log(err);
	});

// 之後:不再有任何回撥!
async function getJson() {
	try {
		let response = await fetch('/users.json');
		let json = await response.json();
		console.log(json);
	} catch (err) {
		console.log(err);
	}
}
複製程式碼

是不是程式碼變簡潔、好看了呢。

玩轉 async 函式

下面介紹了幾種使用 async 函式的場景和方式。

匿名 async 函式

let main = (async function () {
	return await fetch('/');
})();
複製程式碼

async 函式宣告

async function main() {
	return await fetch('/');
}
複製程式碼

async 函式表示式

let main = async function () {
	return await fetch('/');	
};

// 也可以使用箭頭函式哦!
let main = async () => {
	return fetch('/');
};
複製程式碼

當做引數的 async 函式

document.body.addEventListener('click', async function () {
	return await fetch('/');
});
複製程式碼

作為物件和類的方法

// 作為物件方法
let obj = {
	async method() {
		return await fetch('/');
	}
};

// 作為類方法
class MyClass {
	async method() {
		return await fetch('/');
	}
}
複製程式碼

你看到了,async 函式除了自身提供的超炫功能外,TM 跟普通函式使用起來是一樣一樣的!

錯誤處理

傳統 rejected 狀態的 Promise 物件使用 catch 方法捕獲錯誤。而 await 相當於是已經處理了 resolved 狀態下 Promise 物件返回的資料,所以在處理錯誤時,await 藉助了 try/catch:

try {
	let x = await myAsyncFunction();
} catch (err) {
	// Error!
}
複製程式碼

雖然,try/catch 處理錯誤的方式看起來很老套,但是相對於引入 await 給我們帶來的便捷,這根本算不上什麼(要不然還要怎樣)。

等待平行任務

Google 的 Jake Archibald 在 async functions 文件 提出了一個很好的建議----不要同時平行地使用多個 await 語句,這會導致等待時間層層累加,如果可能的話,應該立即發出非同步任務,之後再使用 await 等待任務完成(會節省時間的呦)。

// 一共要花掉 1000ms!
async function series() {
  await wait(500);
  await wait(500);
  return "done!";
}

// 僅花掉 500ms!
async function parallel() {
  const wait1 = wait(500);
  const wait2 = wait(500);
  await wait1;
  await wait2;
  return "done!";
}
複製程式碼

第二種情況讓兩個非同步請求同時發出,第一個請求在 500ms 後結束後,輪到第二個請求的時候,也已經完成並立即就能返回結果。這種情況適應於無依賴的請求之間。

await Promise.all

我最喜歡的 Promise API 的功能之一就是 Promise.all,它會等待所有的 Promise 物件完成之後再處理資料。我們也可以在 Promise.all 上使用 await:

let [foo, bar] = await Promise.all([getFoo(), getBar()]);
複製程式碼

記住,async/await 和 Primose 物件在本質上是一樣的,這是我們能夠使用 await Promise.all 等待多個 resolved 狀態 Promise 物件返回資料的原因。

使用 Promise 介面程式設計仍然很優秀,但是相比於 async 和 await,在維護性上略輸一籌。

(完)

相關文章