介绍
Promises 为我们提供了一种更简单的方法来以顺序方式处理代码中的异步。 考虑到我们的大脑并不是为有效处理异步性而设计的,这是一个非常受欢迎的补充。 Async/await 函数 是 ES2017 (ES8) 的新增功能,它帮助我们在编写完全同步的代码的同时在后台执行异步任务。
使用异步函数实现的功能可以通过将 Promise 与 生成器 组合来重新创建,但异步函数无需任何额外的样板代码即可为我们提供所需的功能。
简单示例
在下面的示例中,我们首先声明一个函数,该函数返回一个承诺,该承诺会在 2 秒后解析为 🤡
的值。 然后,我们声明一个 async 函数和 await 以在将消息记录到控制台之前解析承诺:
function scaryClown() { return new Promise(resolve => { setTimeout(() => { resolve('🤡'); }, 2000); }); } async function msg() { const msg = await scaryClown(); console.log('Message:', msg); } msg(); // Message: 🤡 <-- after 2 seconds
await
是一个新的操作符,用于等待一个 promise 解决或拒绝。 它只能在异步函数中使用。
当涉及多个步骤时,异步函数的力量变得更加明显:
function who() { return new Promise(resolve => { setTimeout(() => { resolve('🤡'); }, 200); }); } function what() { return new Promise(resolve => { setTimeout(() => { resolve('lurks'); }, 300); }); } function where() { return new Promise(resolve => { setTimeout(() => { resolve('in the shadows'); }, 500); }); } async function msg() { const a = await who(); const b = await what(); const c = await where(); console.log(`${ a } ${ b } ${ c }`); } msg(); // 🤡 lurks in the shadows <-- after 1 second
但是需要注意的是,在上面的示例中,每个步骤都是按顺序完成的,每个附加步骤都等待前一个步骤解决或拒绝,然后再继续。 如果您希望这些步骤并行发生,您可以简单地使用 Promise.all 来等待所有承诺都已实现:
// ... async function msg() { const [a, b, c] = await Promise.all([who(), what(), where()]); console.log(`${ a } ${ b } ${ c }`); } msg(); // 🤡 lurks in the shadows <-- after 500ms
Promise.all 在所有传入的 Promise 都解析后返回一个包含解析值的数组。
在上面我们还使用了一些不错的 数组解构 来使我们的代码简洁。
承诺回报
异步函数总是返回一个承诺,所以以下可能不会产生你想要的结果:
async function hello() { return 'Hello Alligator!'; } const b = hello(); console.log(b); // [object Promise] { ... }
由于返回的是一个承诺,你可以这样做:
async function hello() { return 'Hello Alligator!'; } const b = hello(); b.then(x => console.log(x)); // Hello Alligator!
…或者只是这个:
async function hello() { return 'Hello Alligator!'; } hello().then(x => console.log(x)); // Hello Alligator!
不同的形式
So far with our examples we saw the async function as a function declaration, but we can also define async function expressions and async arrow functions:
异步函数表达式
这是我们第一个示例中的异步函数,但定义为函数表达式:
const msg = async function() { const msg = await scaryClown(); console.log('Message:', msg); }
异步箭头函数
这是同样的例子,但这次定义为箭头函数:
const msg = async () => { const msg = await scaryClown(); console.log('Message:', msg); }
错误处理
异步函数的另一个优点是错误处理也是完全同步完成的,使用良好的旧 try...catch 语句。 让我们使用一个会拒绝一半时间的 Promise 来演示:
function yayOrNay() { return new Promise((resolve, reject) => { const val = Math.round(Math.random() * 1); // 0 or 1, at random val ? resolve('Lucky!!') : reject('Nope 😠'); }); } async function msg() { try { const msg = await yayOrNay(); console.log(msg); } catch(err) { console.log(err); } } msg(); // Lucky!! msg(); // Lucky!! msg(); // Lucky!! msg(); // Nope 😠 msg(); // Lucky!! msg(); // Nope 😠 msg(); // Nope 😠 msg(); // Nope 😠 msg(); // Nope 😠 msg(); // Lucky!!
鉴于异步函数总是返回一个 Promise,您还可以像通常使用 catch 语句一样处理未处理的错误:
async function msg() { const msg = await yayOrNay(); console.log(msg); } msg().catch(x => console.log(x));
这种同步错误处理不仅在 Promise 被拒绝时有效,而且在发生实际运行时或语法错误时也有效。 在下面的示例中,第二次调用 msg 函数时,我们传入一个 number 值,该值在其原型链中没有 toUpperCase 方法。 我们的 try...catch 块也捕获了该错误:
function caserUpper(val) { return new Promise((resolve, reject) => { resolve(val.toUpperCase()); }); } async function msg(x) { try { const msg = await caserUpper(x); console.log(msg); } catch(err) { console.log('Ohh no:', err.message); } } msg('Hello'); // HELLO msg(34); // Ohh no: val.toUpperCase is not a function
具有基于 Promise 的 APIS 的异步函数
正如我们在 Fetch API 入门中所展示的,基于 Promise 的 Web API 是异步函数的完美候选者:
async function fetchUsers(endpoint) { const res = await fetch(endpoint); let data = await res.json(); data = data.map(user => user.username); console.log(data); } fetchUsers('https://jsonplaceholder.typicode.com/users'); // ["Bret", "Antonette", "Samantha", "Karianne", "Kamren", "Leopoldo_Corkery", "Elwyn.Skiles", "Maxime_Nienow", "Delphine", "Moriah.Stanton"]
浏览器支持:截至 2020 年,94% of 全球浏览器可以处理 javascript 中的 async/await IE11 和 Opera Mini 除外。
结论
在 Async/await 函数之前, JavaScript 代码依赖于大量异步事件 (例如:对 API 进行大量调用的代码) 最终会陷入所谓的“回调地狱” ” - 一系列很难阅读和理解的函数和回调。
Async 和 await 允许我们编写读取更清晰的异步 JavaScript 代码。