介绍
Promise为我们提供了一种更简单的方法来以顺序方式处理代码中的异步问题。考虑到我们的大脑并不是为有效处理异步性而设计的,这是一个非常受欢迎的补充。Async/await 函数是 ES2017 ( ES8 )的新增功能,它帮助我们在幕后执行异步任务的同时编写完全同步的代码。
使用 async 函数实现的功能可以通过将 promise 与generators结合来重新创建,但是 async 函数为我们提供了我们需要的东西,而无需任何额外的样板代码。
简单示例
在下面的示例中,我们首先声明一个函数,该函数返回一个🤡
2 秒后解析为值的承诺。然后我们声明一个异步函数并在将消息记录到控制台之前等待承诺解决:
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
是一个新的操作符,用于等待解决或拒绝承诺。它只能在异步函数中使用。
当涉及多个步骤时,异步函数的威力变得更加明显:
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来等待所有的Promise完成:
// ...
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将返回一个包含已解决值的数组。
在上面我们还使用了一些很好的数组解构来使我们的代码简洁。
承诺回报
异步函数总是返回一个承诺,所以以下可能不会产生你想要的结果:
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!
不同的形式
到目前为止,在我们的示例中,我们将异步函数视为函数声明,但您也可以定义异步函数表达式和异步箭头函数:
异步函数表达式
这是我们第一个示例中的异步函数,但定义为函数表达式:
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!!
鉴于异步函数总是返回一个承诺,您还可以像通常使用 catch 语句一样处理未处理的错误:
async function msg() {
const msg = await yayOrNay();
console.log(msg);
}
msg().catch(x => console.log(x));
这种同步错误处理不仅在承诺被拒绝时起作用,而且在发生实际运行时或语法错误时也起作用。在下面的例子中,第二次与呼叫我们味精功能我们传递了一个号码,不具有价值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% 的浏览器可以在 javascript 中处理异步/等待。值得注意的例外是 IE11 和 Opera Mini。
结论
在Async/await 函数出现之前,依赖于大量异步事件的 JavaScript 代码(例如:对 API 进行大量调用的代码)最终会陷入所谓的“回调地狱”——一个非常困难的函数和回调链阅读和理解。
Async 和 await 使我们能够编写读取更清晰的异步 JavaScript 代码。