Promises为我们提供了一种更简单的方式可以依次处理代码中的异步问题。考虑到我们的电脑不能有效地处理异步问题,因此这是一个非常受欢迎的补充。Async/await functions是ES2017 (ES8)新增的功能,进一步帮助我们在后台执行异步任务时编写完全同步的代码。
用异步函数实现的功能也可以通过结合promises和generators来实现,但是异步函数无需额外的公式化代码即可为我们提供需要的功能。
简单的例子
在下面的例子中,我们先声明一个函数,这个函数会返回一个在2秒之后解析值为🤡的promise对象。 接着我们声明一个async函数和一个await,在将消息输出到控制台之前等待promise调用resolve:
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调用resolve或reject函数。它只能在异步函数中使用。
当涉及到多个步骤时,异步函数的强大之处变得更加明显:
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的状态都变为已解决,Promise.all
将返回一个包含已解决值的数组。在上面,我们还利用一些数组解构来使我们的代码更简洁。
Promise的返回
异步函数总是返回一个promise对象,因此以下内容可能不会产生你想要的结果:
async function hello() {
return 'Hello Alligator!';
}
const b = hello();
console.log(b); // [object Promise] { ... }
由于返回的是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!!
鉴于异步函数总是返回一个promise对象,你也可以像平时使用catch语句一样处理未处理的错误:
async function msg() {
const msg = await yayOrNay();
console.log(msg);
}
msg().catch(x => console.log(x));
同步的错误处理不仅在promise为拒绝状态时有效,在实际运行时或语法错误发生时也同样有效。在下面的例子中,
第二次调用我们的msg函数时,我们传入一个原型链中没有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 APIS也是一个理想的选择:
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"]
浏览器支持: