在上一篇,我们用 Promise 把“回调地狱”改造成了优雅的链式调用。这已经是一个巨大的进步了。但是,当逻辑复杂时,一长串的 .then()
仍然会降低代码的可读性,我们的大脑依然需要切换到“异步模式”去理解代码。
有没有一种方法,能让我们像写同步代码(一行接一行)那样去写异步代码呢?
答案是肯定的!ES7 (ES2017) 带来了 async/await
,它被誉为 JavaScript 异步编程的“终极解决方案”。
一、async/await
是什么?
首先要明确最重要的一点:async/await
并不是一个全新的东西,它本质上是 Promise 的语法糖。它没有创造新的功能,而是让你用一种更舒服、更直观的方式来使用 Promise。
async
:用于声明一个函数是异步函数。它写在函数定义的前面。async function myFunction() { ... }
const myFunction = async () => { ... }
await
:字面意思是“等待”。它只能用在async
函数内部。await
会暂停async
函数的执行,等待它后面的 Promise 完成,然后返回 Promise 的成功结果。
二、用 async/await
重写 Promise 链
让我们再次请出上一篇的例子,看看用 async/await
如何实现。
【上一篇的 Promise 写法】
function fetchDataWithPromise() {
return ajaxPromise('api/user/1')
.then(user => {
console.log('获取到用户:', user.name);
return ajaxPromise('api/posts/' + user.id);
})
.then(posts => {
console.log('获取到帖子:', posts.length, '篇');
return ajaxPromise('api/comments/' + posts[0].id);
})
.then(comments => {
console.log('获取到评论');
return comments;
})
.catch(err => {
console.error('链式调用中发生错误:', err);
throw err; // 向上抛出错误
});
}
【现在我们这么写 (ES6+ async/await
)】
// 1. 在函数前加上 async
async function fetchDataWithAsync() {
// 2. 使用我们熟悉的 try...catch 来处理错误
try {
// 3. 使用 await 等待 Promise 完成,并直接用变量接收结果
const user = await ajaxPromise('api/user/1');
console.log('获取到用户:', user.name);
const posts = await ajaxPromise('api/posts/' + user.id);
console.log('获取到帖子:', posts.length, '篇');
const comments = await ajaxPromise('api/comments/' + posts[0].id);
console.log('获取到评论');
console.log('所有数据获取完毕!');
return comments; // async 函数的返回值会自动包装成一个 resolved Promise
} catch (err) {
// 任何一个 await 的 Promise 失败,都会被 catch 捕获
console.error('在 async 函数中捕获到错误:', err);
// 可以在这里向上抛出错误,让调用者知道
throw err;
}
}
// 如何调用 async 函数?
// 因为 async 函数本身返回一个 Promise,所以可以像普通 Promise 一样使用
fetchDataWithAsync()
.then(finalResult => {
console.log('Async 函数成功完成,最终结果:', finalResult);
})
.catch(error => {
console.log('调用者捕获到 async 函数的错误');
});
对比一下,async/await
的优势显而易见:
- 代码像同步:完全消除了
.then()
的嵌套和链条,代码从上到下执行,逻辑清晰得就像在读一篇普通文章。 - 变量赋值更自然:可以直接用
const
或let
来接收异步操作的结果,无需在回调函数之间通过return
传递。 - 错误处理更统一:使用我们早已熟悉的
try...catch
结构,可以将多个await
的错误集中处理,非常直观。 - 调试更方便:你可以在
await
的下一行打断点,清晰地看到异步操作返回的结果,就像调试同步代码一样。
三、async/await
的使用要点
await
必须在async
函数内使用。在全局作用域或普通函数中直接使用await
会导致语法错误。(注:现代浏览器和 Node.js 已支持顶层await
,但为兼容性考虑,在函数中使用仍是主流)。await
等待的是 Promise。如果你await
一个非 Promise 的值,它会立即返回这个值本身。async
函数总是返回一个 Promise。即使你没有显式return
一个 Promise,它也会将你的返回值包装成一个 resolved Promise。如果函数内部抛出错误,则返回一个 rejected Promise。
四、并发请求 (Promise.all
)
如果多个异步操作之间没有依赖关系,让它们串行执行(一个等一个)会很慢。这时,我们可以用 Promise.all
来让它们并发执行。
async function fetchParallelData() {
try {
const [userData, postsData] = await Promise.all([
ajaxPromise('api/user/1'),
ajaxPromise('api/all_posts')
]);
console.log('用户信息:', userData);
console.log('帖子列表:', postsData);
} catch (err) {
console.error('并发请求失败:', err);
}
}
Promise.all
接收一个 Promise 数组,当所有 Promise 都成功时,它会返回一个包含所有结果的数组。如果其中任何一个失败,它会立即失败。
总结
async/await
是建立在 Promise 之上的语法糖,它让我们能够以同步的方式编写异步代码,是现代 JavaScript 开发处理异步的首选方案。
从“回调地狱”到“Promise 链”,再到“async/await
”,我们见证了 JavaScript 异步编程的完美进化。
掌握了 async/await
,你就掌握了现代前端处理异步的核心武器。在下一篇,我们将探讨代码的组织方式——Class
与官方模块化方案,为我们接入现代框架开发做好最后的准备。敬请期待!