JavaScript async/await指南
JavaScript异步编程指南:async/await的使用场景与规范
一、async/await是什么?为什么它是异步编程的救星?
1. 核心概念
async
和await
是ES2017推出的异步编程语法糖,本质是Promise的语法简化版:
async
修饰的函数会隐式返回Promise,相当于自动给返回值套上Promise.resolve()
await
只能在async
函数内使用,能让代码"暂停"等待Promise结果,使异步代码拥有同步写法的直观性
2. 对比传统异步方案
回调函数:
fs.readFile('data.txt', (err, data) => {if (err) throw err;console.log(data);
});
Promise链式调用:
fetch('api/data').then(res => res.json()).then(data => console.log(data)).catch(err => console.error(err));
async/await:
async function getData() {try {const res = await fetch('api/data');const data = await res.json();console.log(data);} catch (err) {console.error(err);}
}
优势:代码结构更接近同步逻辑,避免回调地狱和Promise多层嵌套,错误处理更统一。
二、核心用法详解
1. async函数基础语法
// 定义async函数,返回Promise
async function getUser() {return { id: 1, name: "张三" };
}// 等价于
function getUser() {return Promise.resolve({ id: 1, name: "张三" });
}// 调用方式
getUser().then(user => {console.log(user.name); // 输出:张三
});
2. await关键字的正确姿势
async function showDelayMessage() {// 封装延迟Promiseconst delay = ms => new Promise(resolve => setTimeout(resolve, ms));console.log("开始执行");await delay(2000); // 代码在此暂停2秒console.log("2秒后执行");const message = await new Promise(resolve => {resolve("延迟消息");});console.log(message); // 输出:延迟消息
}showDelayMessage();
3. 错误处理最佳实践
async function fetchData(url) {try {// 等待请求响应const response = await fetch(url);// 检查响应状态if (!response.ok) {throw new Error(`请求失败: ${response.status}`);}// 等待数据解析const data = await response.json();return data;} catch (error) {console.error("数据获取失败:", error);throw error; // 可选:向上抛出错误}
}
三、六大典型使用场景与实战案例
1. 场景一:网络请求(最常用场景)
需求:获取用户信息后再获取该用户的帖子
async function getUserAndPosts(userId) {try {// 顺序执行异步操作const userResponse = await fetch(`/api/users/${userId}`);const user = await userResponse.json();const postsResponse = await fetch(`/api/posts?userId=${user.id}`);const posts = await postsResponse.json();return { user, posts };} catch (error) {console.error("请求失败:", error);}
}
2. 场景二:并行请求优化
需求:同时获取用户、帖子、评论数据
async function fetchAllData() {try {// 并行发起多个请求const userPromise = fetch("/api/user/1").then(res => res.json());const postsPromise = fetch("/api/posts").then(res => res.json());const commentsPromise = fetch("/api/comments").then(res => res.json());// 等待所有请求完成const [user, posts, comments] = await Promise.all([userPromise,postsPromise,commentsPromise]);return { user, posts, comments };} catch (error) {console.error("任一请求失败:", error);}
}
3. 场景三:定时器延迟控制
需求:实现一个可复用的延迟函数
// 封装延迟Promise
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));async function taskWithDelay() {console.log("任务开始");// 等待3秒await delay(3000);console.log("3秒后执行");// 再等待1秒await delay(1000);console.log("4秒后执行");
}
4. 场景四:文件系统操作(Node.js)
需求:读取配置文件并解析
const fs = require('fs').promises; // Node.js内置Promise化APIasync function loadConfig() {try {// 读取文件内容const content = await fs.readFile('./config.json', 'utf-8');// 解析JSONconst config = JSON.parse(content);return config;} catch (error) {console.error("配置加载失败:", error);throw new Error("配置文件异常,请检查路径");}
}
5. 场景五:表单提交与验证
需求:前端表单提交前验证并发送请求
async function handleFormSubmit(formData) {// 前端验证if (!formData.username || !formData.password) {throw new Error("用户名和密码不能为空");}try {// 发送提交请求const response = await fetch('/api/login', {method: 'POST',body: JSON.stringify(formData),headers: { 'Content-Type': 'application/json' }});const result = await response.json();if (result.success) {return "登录成功";} else {throw new Error(result.message || "登录失败");}} catch (error) {console.error("提交失败:", error);throw error;}
}
6. 场景六:处理流数据(Node.js)
需求:读取大文件并逐行处理
const fs = require('fs');
const readline = require('readline');async function processLargeFile(filePath) {try {const fileStream = fs.createReadStream(filePath);const rl = readline.createInterface({input: fileStream,crlfDelay: Infinity});let lineCount = 0;// 逐行处理for await (const line of rl) {lineCount++;// 处理每行数据console.log(`行${lineCount}: ${line}`);}console.log(`处理完成,共${lineCount}行`);} catch (error) {console.error("文件处理失败:", error);}
}
四、企业级代码规范与最佳实践
1. 命名与定义规范
- 函数命名:async函数建议添加
Async
后缀(可选),如fetchUserAsync()
- 定义方式:优先使用普通函数定义,避免箭头函数(可读性更好)
// 推荐 async function loadData() {}// 不推荐 const loadData = async () => {}
- 变量命名:await后的变量名建议体现异步操作含义,如
await fetchResponse
2. 错误处理规范
- 必用try…catch:所有await操作必须包裹在try块中,catch统一处理错误
async function main() {try {const data = await fetchData();await processData(data);} catch (error) {console.error("全局错误捕获:", error);// 记录错误日志(如上报到监控系统)sendErrorToMonitor(error);} }
- 错误分类处理:根据错误类型做不同处理(示例)
async function handleError() {try {// ...} catch (error) {if (error instanceof NetworkError) {console.log("网络错误,重试中...");// 重试逻辑} else if (error instanceof AuthError) {console.log("认证失败,跳转登录页");// 跳转逻辑} else {console.error("未知错误:", error);}} }
3. 性能优化规范
- 并行任务用Promise.all:避免顺序等待浪费时间
// 差:顺序执行(总耗时=3s+2s=5s) async function bad() {await delay(3000);await delay(2000); }// 好:并行执行(总耗时=3s) async function good() {await Promise.all([delay(3000), delay(2000)]); }
- 限制并发数量:处理大量并行请求时控制并发数(示例)
async function fetchWithConcurrency(urls, maxConcurrent) {const results = [];const fetchQueue = urls.map(url => {return async () => {try {const res = await fetch(url);results.push(await res.json());} catch (err) {results.push(err);}};});// 分批执行for (let i = 0; i < fetchQueue.length; i += maxConcurrent) {const batch = fetchQueue.slice(i, i + maxConcurrent);await Promise.all(batch.map(fetchFn => fetchFn()));}return results; }
4. 代码风格规范
- 一行一await:每个await单独一行,提高可读性
// 推荐 const response = await fetch(url); const data = await response.json();// 不推荐 const data = await await fetch(url).then(res => res.json());
- 合理使用return:及时return避免不必要的等待
async function getData() {if (cacheAvailable) {return getFromCache(); // 直接返回缓存数据,无需等待}const data = await fetchFromServer();return data; }
5. 特殊场景处理
- 处理超时:为await操作添加超时控制
// 封装超时Promise function timeout(ms) {return new Promise((_, reject) => {setTimeout(() => reject(new Error("操作超时")), ms);}); }async function fetchWithTimeout(url, timeoutMs) {try {return await Promise.race([fetch(url),timeout(timeoutMs)]);} catch (error) {console.error("请求超时:", error);throw error;} }
- 处理取消操作:使用AbortController取消异步请求
async function fetchWithAbort(url) {const controller = new AbortController();const signal = controller.signal;// 5秒后取消请求setTimeout(() => {controller.abort();console.log("请求已取消");}, 5000);try {const response = await fetch(url, { signal });return await response.json();} catch (error) {if (error.name === "AbortError") {console.log("请求被取消");} else {console.error("请求失败:", error);}} }
五、浏览器兼容性与解决方案
1. 主流浏览器支持情况
浏览器 | 最低支持版本 | 支持时间 |
---|---|---|
Chrome | 55+ | 2016年12月 |
Firefox | 52+ | 2017年3月 |
Safari | 11+ | 2017年9月 |
Edge | 15+ | 2017年4月 |
Opera | 42+ | 2016年12月 |
2. 兼容性解决方案
方案一:使用babel转译
- 安装依赖:
npm install @babel/core @babel/preset-env @babel/cli -D
- 创建
.babelrc
配置文件:{"presets": [["@babel/preset-env", {"targets": {"browsers": ["last 2 versions", "ie >= 11"]}}]] }
- 转译命令:
npx babel src -d dist
方案二:引入polyfill
- 安装
regenerator-runtime
:npm install regenerator-runtime
- 在代码中引入:
import "regenerator-runtime/runtime";
六、进阶技巧与面试高频问题
1. 如何处理Promise.all中的部分失败?
async function fetchWithPartialFail(urls) {const results = [];for (const url of urls) {try {const res = await fetch(url);results.push(await res.json());} catch (error) {results.push({ error: true, message: error.message });console.log(`请求${url}失败`, error);}}return results;
}
2. await能在普通函数中使用吗?为什么?
不能。因为await
必须在async
函数内使用,本质上async
函数会被编译为生成器函数(Generator),而await
是生成器中yield
的语法糖,普通函数无法使用该特性。
3. async/await和Promise的本质区别?
async/await
是语法糖,底层仍基于Promise实现async/await
让异步代码拥有同步的写法,更符合人类思维习惯async/await
通过try...catch
统一处理错误,比Promise的.catch()
更直观
七、总结:async/await使用三原则
- 能用async/await就不用传统Promise:除非需要处理极其复杂的异步流程控制
- 每个await必配try…catch:避免未捕获的异步错误导致程序崩溃
- 合理选择并行/顺序执行:IO密集型任务用Promise.all并行处理,计算密集型任务考虑Web Worker
掌握async/await不仅能写出更优雅的异步代码,还能提升效率。