本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net
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() {
// 封装延迟Promise
const 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化API
async function loadConfig() {
try {
// 读取文件内容
const content = await fs.readFile('./config.json', 'utf-8');
// 解析JSON
const 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 不仅能写出更优雅的异步代码,还能提升效率。