[译] JavaScript async 和 await

2,060 阅读4分钟

原文链接: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,在维护性上略输一筹。

(完)