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,在维护性上略输一筹。
(完)