JS 入门通关手册(25):async/await:Promise 的语法糖,异步代码同步化
上一篇我们掌握了 Promise,它彻底解决了回调地狱的问题,让异步代码可以链式书写,变得清晰可维护。但链式调用如果层级过多,依然会有一定的冗余感。
ES7 推出的 async/await,是 Promise 的 “语法糖”—— 它没有新增任何底层逻辑,完全基于 Promise 实现,却能让异步代码写得和同步代码一模一样,可读性、可维护性再上一个台阶,也是现在实际开发中最常用的异步写法。
本文全程贴合前 5 篇风格,无多余格式,直接适配聊天窗口查看,重点讲解 async/await 的基本用法、核心特性、错误处理,以及和 Promise 的关联,所有代码可直接复制运行,兼顾入门和面试。
一、先搞懂:async/await 是什么?
一句话总结:async/await 是基于 Promise 的语法糖,目的是简化 Promise 的链式调用,让异步代码的写法更接近同步代码。
核心关系:
async用来修饰函数,表明这个函数是异步函数,函数内部可以使用await;await只能用在async函数内部,用来 “等待” 一个 Promise 完成(成功 / 失败),并返回 Promise 的结果;- 本质:
await就是 “等待” Promise 的then结果,async函数的返回值,默认会被包装成一个成功的 Promise。
对比感受(同样的异步逻辑):
Promise 链式写法(上一篇)
javascript
运行
// 模拟接口请求
function requestData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("接口返回的数据");
}, 1000);
});
}
// Promise 链式调用
requestData()
.then(res => {
console.log("获取到数据:", res);
return "处理后的数据";
})
.then(processedRes => {
console.log("处理完成:", processedRes);
});
async/await 写法(更简洁)
javascript
运行
// 模拟接口请求(和上面一致,还是返回 Promise)
function requestData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve("接口返回的数据");
}, 1000);
});
}
// async 修饰异步函数
async function getData() {
// await 等待 Promise 完成,直接拿到 resolve 的结果
const res = await requestData();
console.log("获取到数据:", res);
const processedRes = "处理后的数据";
console.log("处理完成:", processedRes);
return processedRes; // 异步函数的返回值,会被包装成 Promise
}
// 调用异步函数(和调用 Promise 一样,可加 then)
getData().then(res => console.log("异步函数返回:", res));
可以看到:async/await 去掉了繁琐的 then 链式,代码自上而下执行,和同步代码的逻辑完全一致,一眼就能看懂。
二、async/await 基本用法(必掌握)
1. 基础语法:async 修饰函数
- 函数前面加
async,函数就变成异步函数; - 异步函数内部可以使用
await,外部调用异步函数,会返回一个 Promise; - 即使异步函数没有 return,也会返回
Promise.resolve(undefined)。
javascript
运行
// 1. 普通 async 函数(无 return)
async function fn1() {
console.log("异步函数执行");
}
fn1(); // 执行函数,返回 Promise.resolve(undefined)
fn1().then(() => console.log("异步函数执行完成"));
// 2. 有 return 的 async 函数
async function fn2() {
return "我是返回值"; // 等价于 return Promise.resolve("我是返回值")
}
fn2().then(res => console.log(res)); // 输出:我是返回值
// 3. 箭头函数写法(常用)
const fn3 = async () => {
return await requestData(); // 结合 await 使用
};
2. 核心用法:await 等待 Promise
await后面必须跟一个 Promise 对象(如果不是,会自动包装成Promise.resolve(值));await会 “暂停” 异步函数的执行,等 Promise 完成(fulfilled)后,继续执行后续代码,并返回 Promise 的resolve结果;- 注意:
await只是 “暂停当前 async 函数”,不会阻塞整个 JS 线程(因为底层还是 Promise,属于异步微任务)。
javascript
运行
// 模拟两个接口请求,有依赖关系(先请求1,再请求2)
function request1() {
return new Promise(resolve => {
setTimeout(() => resolve("请求1返回的数据"), 1000);
});
}
function request2(data) {
return new Promise(resolve => {
setTimeout(() => resolve(`请求2接收的数据:${data}`), 1000);
});
}
// async/await 写法(同步式逻辑,解决依赖)
async function getTwoData() {
console.log("开始请求1");
const data1 = await request1(); // 等待请求1完成
console.log("请求1完成,开始请求2");
const data2 = await request2(data1); // 用请求1的结果请求2
console.log("请求2完成,最终数据:", data2);
return data2;
}
getTwoData();
// 输出顺序(1秒后输出请求1完成,再1秒后输出请求2完成):
// 开始请求1
// 请求1完成,开始请求2
// 请求2完成,最终数据: 请求2接收的数据:请求1返回的数据
3. 无依赖异步请求:并行执行
如果多个异步请求之间没有依赖关系(可以同时发起),直接用多个 await 会导致 “串行执行”(浪费时间),此时可以结合 Promise.all() 实现并行,提升效率。
javascript
运行
// 三个无依赖的接口请求
function requestA() {
return new Promise(resolve => setTimeout(() => resolve("A数据"), 1000));
}
function requestB() {
return new Promise(resolve => setTimeout(() => resolve("B数据"), 1000));
}
function requestC() {
return new Promise(resolve => setTimeout(() => resolve("C数据"), 1000));
}
// 错误写法:串行执行,总耗时 3 秒
async function getSerialData() {
const a = await requestA();
const b = await requestB();
const c = await requestC();
console.log(a, b, c); // 总耗时 ~3000ms
}
// 正确写法:并行执行,总耗时 1 秒(推荐)
async function getParallelData() {
// 先同时发起所有请求,得到三个 Promise
const promiseA = requestA();
const promiseB = requestB();
const promiseC = requestC();
// 等待所有 Promise 完成(并行)
const [a, b, c] = await Promise.all([promiseA, promiseB, promiseC]);
console.log(a, b, c); // 总耗时 ~1000ms
}
getParallelData();
三、错误处理:try/catch(核心)
Promise 用 catch() 捕获错误,而 async/await 因为写法和同步一致,所以用 try/catch 语句 捕获错误,更符合同步代码的错误处理逻辑,也更简洁。
1. 基本错误处理(单个 await)
javascript
运行
function requestData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟请求失败
reject("接口请求失败,网络错误");
}, 1000);
});
}
async function getData() {
try {
// 可能出错的代码(await 等待的 Promise 可能 reject)
const res = await requestData();
console.log("获取数据:", res);
} catch (err) {
// 捕获所有错误(包括 Promise reject 和代码本身的错误)
console.log("出错了:", err);
}
}
getData(); // 输出:出错了:接口请求失败,网络错误
2. 多个 await 的错误处理(统一捕获)
如果一个 async 函数中有多个 await,只要有一个 Promise 失败,整个函数就会中断,错误会被 catch 统一捕获。
javascript
运行
async function getMultiData() {
try {
const data1 = await request1(); // 假设请求1成功
const data2 = await request2(data1); // 假设请求2失败
const data3 = await request3(data2); // 不会执行(因为请求2失败,函数中断)
console.log(data3);
} catch (err) {
// 捕获请求2的错误
console.log("出错了:", err);
}
}
3. 单独捕获某个 await 的错误
如果希望某个 await 的错误不影响其他 await 执行,可以在该 await 外层单独加 try/catch。
javascript
运行
async function getMultiData() {
try {
const data1 = await request1();
// 单独捕获 request2 的错误
let data2;
try {
data2 = await request2(data1);
} catch (err) {
console.log("request2 出错:", err);
data2 = "默认数据"; // 给默认值,避免后续代码报错
}
// 即使 request2 失败,request3 依然会执行
const data3 = await request3(data2);
console.log(data3);
} catch (err) {
console.log("其他错误:", err);
}
}
四、async/await 与 Promise 的区别(面试常考)
表格
| 对比维度 | Promise | async/await |
|---|---|---|
| 本质 | 异步对象,底层实现异步逻辑 | 语法糖,基于 Promise 实现,无新增底层 |
| 写法 | 链式调用(then/catch) | 同步式写法(try/catch) |
| 可读性 | 比回调函数好,链式过多仍显冗余 | 最优,和同步代码逻辑一致,易读易维护 |
| 错误处理 | 用 catch () 捕获,链式中需单独处理 | 用 try/catch 捕获,统一或单独处理均可 |
| 调试 | 链式调用调试相对麻烦 | 同步式写法,调试更简单(可逐行断点) |
核心结论:async/await 不是替代 Promise,而是对 Promise 的优化。所有 async/await 能实现的功能,Promise 都能实现;但 Promise 能实现的(如并行请求),async/await 结合 Promise API(all/race)也能实现,且写法更简洁。
五、高频坑点与面试题
坑点 1:await 只能用在 async 函数中
javascript
运行
// 错误写法:await 用在普通函数中
function fn() {
const res = await requestData(); // 报错:await is only valid in async functions
}
// 正确写法:函数加 async
async function fn() {
const res = await requestData();
}
坑点 2:async 函数返回值一定是 Promise
javascript
运行
async function fn() {
return "hello"; // 等价于 return Promise.resolve("hello")
}
console.log(fn()); // 输出:Promise {<fulfilled>: 'hello'},不是直接的 "hello"
// 想要拿到返回值,必须用 then 或 await
fn().then(res => console.log(res)); // hello
async function fn2() {
const res = await fn();
console.log(res); // hello
}
坑点 3:多个 await 串行执行,浪费时间
如前面 “并行执行” 的案例,无依赖的多个 await 不要依次写,会导致串行执行,需结合 Promise.all() 实现并行,提升效率。
面试题 1:async/await 是什么?它和 Promise 的关系是什么?
答:async/await 是 ES7 推出的、基于 Promise 的语法糖,核心作用是简化 Promise 的链式调用,让异步代码写法更接近同步代码。它没有新增异步底层逻辑,完全依赖 Promise 实现,async 函数的返回值会自动包装成 Promise,await 本质是等待 Promise 的 then 结果。
面试题 2:async/await 如何处理错误?
答:用 try/catch 语句处理错误。将需要等待的 await 代码放在 try 块中,一旦某个 await 对应的 Promise 被 reject,或者代码本身出现错误,就会进入 catch 块,捕获并处理错误。如果需要单独处理某个 await 的错误,可以在该 await 外层嵌套 try/catch。
面试题 3:await 后面跟的不是 Promise 会怎么样?
答:如果 await 后面跟的不是 Promise 对象,JS 会自动将其包装成一个成功的 Promise,即 Promise.resolve(值),await 会直接返回这个值。
javascript
运行
async function fn() {
const res1 = await 123; // 等价于 await Promise.resolve(123)
const res2 = await "hello"; // 等价于 await Promise.resolve("hello")
console.log(res1, res2); // 123 hello
}
面试题 4:async/await 比 Promise 好在哪里?
答:1. 可读性更好,写法和同步代码一致,避免了 Promise 链式调用的冗余;2. 错误处理更直观,用 try/catch 统一处理,比 Promise 多个 catch 更简洁;3. 调试更方便,同步式写法可逐行断点,无需在 then 中调试。
六、总结(核心要点)
- async/await 是 Promise 的语法糖,基于 Promise 实现,无新增底层逻辑;
- async 修饰函数,表明是异步函数,返回值默认包装成 Promise;
- await 只能用在 async 函数内部,等待 Promise 完成,返回 resolve 结果;
- 错误处理用 try/catch,可统一捕获,也可单独捕获;
- 无依赖异步请求,用
Promise.all()+ await 实现并行,提升效率; - 实际开发中,async/await 是首选异步写法,结合 Promise API 可处理所有异步场景。
到这里,JS 异步编程的核心(回调函数 → Promise → async/await)就全部讲完了。这三者是递进关系,回调是基础,Promise 解决回调地狱,async/await 优化 Promise 写法,掌握这三者,就能轻松应对所有前端异步开发场景。
下一篇我们将进入 JS 数组的高级用法,讲解数组常用的高阶函数(map、filter、reduce 等),这些是前端开发中高频使用的工具,也是面试重点。
📌 所有代码可直接复制到浏览器控制台运行,动手实操,重点感受 async/await 同步式的异步写法,以及 try/catch 的错误处理逻辑。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)