JavaScript 异步编程指南:async/await 与 Promise 该怎么选?
在 JavaScript 开发中,异步操作就像家常便饭 —— 从调用后端 API 到读取本地文件,几乎无处不在。但很多开发者都会困惑:到底该用 Promise 的链式调用,还是 async/await 语法?其实答案很简单:没有绝对的好坏,只有场景的适配。
今天我们就用实际案例聊聊,这两种异步写法各自适合什么场景,以及如何在项目中混搭使用,让代码既高效又易读。
先搞懂:两者不是对立关系
很多人以为 async/await 是 Promise 的替代品,其实大错特错。async/await 本质是 Promise 的语法糖,它的底层依然是 Promise 实现。就像用for...of遍历数组比forEach更直观一样,async/await 让异步代码看起来更像同步代码。
先看个最简单的对比:
// Promise写法
fetchData().then(data => {
return processData(data);
}).then(result => {
console.log(result);
}).catch(err => {
console.error(err);
});
// async/await写法
async function handleData() {
try {
const data = await fetchData();
const result = await processData(data);
console.log(result);
} catch (err) {
console.error(err);
}
}
两者功能完全一致,但 async/await 的线性结构更符合人类的阅读习惯 —— 这也是它被广泛采用的核心原因。
优先用 async/await 的 3 种场景
什么时候用 async/await 更合适?记住一个原则:当异步操作需要按顺序执行,或者逻辑中有较多条件判断时。
1. 线性异步流程(一步接一步)
最典型的场景是依赖前一步结果的异步操作。比如先登录获取 token,再用 token 获取用户信息,最后用用户信息加载权限配置:
// 用async/await,逻辑一目了然
async function initApp(username, password) { // 补充参数定义,避免未定义变量
try {
const token = await login(username, password);
const userInfo = await getUserInfo(token); // 依赖token
const permissions = await getPermissions(userInfo.role); // 依赖userInfo
renderApp(permissions);
} catch (err) {
showError(err);
}
}
如果用 Promise 链式调用写,虽然能实现,但嵌套越深(比如再加两步),可读性会明显下降。
2. 包含条件判断的异步逻辑
当异步流程中需要根据结果做分支判断时,async/await 的优势更明显。比如:
async function checkAndUpdate() {
const currentVersion = await getCurrentVersion();
// 同步的条件判断,自然融入异步流程
if (currentVersion < '2.0') {
const updateInfo = await fetchUpdateInfo();
if (updateInfo && updateInfo.force) { // 增加可选链,避免updateInfo为undefined时报错
await showForceUpdateDialog();
} else {
await showOptionalUpdateToast();
}
}
}
这段代码用 Promise 写会嵌套多层then,而 async/await 让同步逻辑和异步操作无缝衔接,就像写同步代码一样自然。
3. 需要中断执行的场景
有时候我们需要在异步流程中提前返回(比如参数无效时),async/await 的写法更直观:
async function submitForm(data) {
// 同步校验
if (!data?.email) { // 增加可选链,避免data为null/undefined时报错
showError('邮箱不能为空');
return; // 直接中断
}
// 异步操作
try {
const validateResult = await validateRemote(data);
if (!validateResult.success) {
showError(validateResult.message);
return; // 校验失败,中断
}
await submitData(data);
showSuccess();
} catch (err) {
handleError(err);
}
}
用 Promise 的话,需要在每个then里处理条件判断,代码会更零散。
优先用 Promise 的 3 种场景
虽然 async/await 很方便,但有些场景下,Promise 的原生 API(如Promise.all、Promise.race)更适合,甚至不可替代。
1. 并行执行多个异步操作
当多个异步任务互不依赖时,用Promise.all并行执行能大幅提高效率。比如同时加载列表数据和筛选条件:
async function loadDashboard() {
// 两个请求并行执行,总耗时是较慢那个的时间
const [products, categories] = await Promise.all([
fetchProducts(),
fetchCategories()
]);
renderProducts(products);
renderFilters(categories);
}
如果用await逐个调用,会变成串行执行(总耗时是两者之和),完全没必要:
// 不推荐:串行执行,浪费时间
const products = await fetchProducts();
const categories = await fetchCategories(); // 等第一个完成才开始
2. 需要超时控制的异步操作
Promise.race可以实现 “谁先完成就用谁的结果”,非常适合超时控制。比如 “3 秒内没返回就显示加载失败”:
// 封装一个带超时的异步函数
function withTimeout(promise, timeoutMs = 3000) {
let timer; // 将timer提升到外部作用域
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(new Error('请求超时'));
}, timeoutMs);
});
return Promise.race([
promise,
timeoutPromise
]).finally(() => clearTimeout(timer)); // 确保始终清除定时器
}
// 使用
async function loadData() {
try {
const data = await withTimeout(fetchLargeData());
render(data);
} catch (err) {
showError(err.message); // 可能是超时错误
}
}
这段代码用 async/await 无法实现,必须依赖 Promise 的race方法。
3. 处理动态数量的异步任务
当需要处理不确定数量的异步操作(比如批量上传多个文件),Promise.all是最佳选择:
async function uploadFiles(files) {
if (!files?.length) return; // 增加空值判断,避免空数组或undefined时执行无效操作
// 生成一个包含所有上传Promise的数组
const uploadPromises = files.map(file => {
return uploadFile(file); // 每个文件的上传是异步操作
});
// 等待所有文件上传完成
const results = await Promise.all(uploadPromises);
// 处理结果
const successCount = results.filter(r => r?.success).length; // 增加可选链容错
showMessage(`成功上传 ${successCount}/${files.length} 个文件`);
}
这种动态场景下,Promise 的数组处理能力比 async/await 更高效。
混搭使用:发挥各自优势
实际开发中,两者往往结合使用效果最好。比如先并行获取基础数据,再串行处理后续逻辑:
async function buildReport() {
// 第一步:并行获取不相关的数据(提高效率)
const [users, orders, products] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchProducts()
]);
// 第二步:串行处理依赖关系的逻辑
const userStats = await calculateUserStats(users);
const orderSummary = await generateOrderSummary(orders, userStats); // 依赖userStats
const report = await compileReport(orderSummary, products); // 依赖前两者
return report;
}
这段代码先用Promise.all并行请求,节省时间;再用 async/await 处理有依赖的串行逻辑,兼顾效率和可读性。
避坑指南:这些错误别犯
- 不要在循环中直接用 await
循环中用await会导致串行执行,如需并行,改用Promise.all:
// 错误:串行执行,慢!
for (const file of files) {
await uploadFile(file);
}
// 正确:并行执行,快!
await Promise.all(files.map(file => uploadFile(file)));
- 别忘了 try/catch
async/await 中任何await的 Promise reject 都会触发异常,必须用try/catch捕获,否则会导致程序崩溃。
- 不要把 async 函数当同步函数用
async 函数永远返回 Promise,调用时必须用await或.then处理,直接调用会拿到 Promise 对象而非结果。
- 避免过度封装
简单的异步操作(比如单个请求)没必要包成 async 函数,直接返回 Promise 更简洁。
- 注意 Promise.all 的失败快速失败特性
Promise.all中任何一个 Promise reject 都会立即触发整个 Promise reject,如需等待所有结果(无论成功失败),可使用Promise.allSettled:
// 等待所有任务完成,无论成功失败
// 文件上传场景优化
const results = await Promise.allSettled(uploadPromises);
const failedFiles = results
.filter(r => r.status === 'rejected')
.map((r, i) => ({ file: files[i].name, error: r.reason }));
总结:一句话记住用法
- 用 async/await:处理线性依赖、包含条件判断、需要中断的异步流程;
- 用 Promise API:处理并行任务(
all)、超时控制(race)、动态异步数组; - 最佳实践:两者结合,并行任务用
Promise.all,后续逻辑用 async/await 串联。
说到底,选择的核心是可读性和效率—— 哪种写法让团队成员更容易理解,哪种方式能让程序跑得更快,就用哪种。技术没有绝对的好坏,适合场景的才是最好的。
JavaScript 异步编程指南:async/await 与 Promise 该怎么选?的更多相关文章
- 异步编程(async&await)
前言 本来这篇文章上个月就该发布了,但是因为忙 QuarkDoc 一直没有时间整理,所以耽搁到今天,现在回归正轨. C# 5.0 虽然只引入了2个新关键词:async和await.然而它大大简化了异步 ...
- 学习迭代器实现C#异步编程——仿async/await(一)
.NET 4.5的async/await真是个神奇的东西,巧妙异常以致我不禁对其实现充满好奇,但一直难以窥探其门径.不意间读了此篇强文<Asynchronous Programming in C ...
- python3.6以上 asyncio模块的异步编程模型 async await语法
这是python3.6以上版本的用法,本例是python3.7.2编写使用asyncio模块的异步编程模型,生产这消费者,异步生产,用sleep来代替IO等待使用async和await语法来进行描述a ...
- C# 异步编程(async&await)
同步:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去 异步:异步是指进程不需要一直等下去,而是继续执行下面的操作 ...
- 基于任务的异步编程(Task,async,await)
这节讲一下比较高级的异步编程用法Task,以及两个异步关键字async和await. Task是在C#5.0推出的语法,它是基于任务的异步编程语法,是对Thread的升级,也提供了很多API,先看一下 ...
- JavaScript 异步编程(二):Promise
PromiseState Promise 有一个 [[PromiseState]] 属性,表示当前的状态,状态有 pending 和 fulfill 以及 reject. 从第一个 Promise 开 ...
- [译] 回调地狱——JavaScript异步编程指南
原文:Callback Hell 什么是 “回调地狱”? 在 JavaScript 中,我们经常通过回调来实现异步逻辑,一旦嵌套层级多了,代码结构就容易变得很不直观,最后看起来像这样: fs.read ...
- c# 异步编程demo (async await)
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.W ...
- ASP.NET 异步编程之Async await
本文重点介绍的是.NET Framework4.5 推出的异步编程方案 async await 请先看个5分钟的微软演示的视频:视频地址: https://channel9.msdn.com/Blo ...
- JavaScript异步编程——Async/Await vs Promise
兼容性 提醒一下各位,Node 现在从版本 7.6 开始就支持 async/await 了.而就在前几天,Node 8已经正式发布了,你可以放心地使用它. 如果你还没有试过它,这里有一堆带有示例的理由 ...
随机推荐
- v-bind,v-if,v-for,v-on,v-model基本用法
总结: 1.v-bind绑定数据:标签属性v-bind:title='xxx',简写:title='xxx', 标签内容{{xxx}} <span :title='message'>{{m ...
- surging 集成SuperSocket预发布版本2.0
一.概述 周末在家试着扩展SuperSocket,因为之前都是只支持.net framework, 后面出现支持.NET CORE 的SuperSocket 2.0 ,然后集成进来和dotnetty ...
- blk_mq多队列块设备浅析
1. 为什么要使用多队列 在主机中,多cpu运行多个线程,每个线程都能和文件系统交互,文件系统层也是用多线程和bio层交互,但是,块设备层只有一个队列: 在块设备层,来自多个cpu的bio请求被放在同 ...
- Qt 官网开源最新版下载安装保姆级教程【2024-8-4 更新】
➤ 什么是Qt(了解请跳过) ➥ Qt 基本介绍 时至今日,Qt 已经经历了诸多变化.并且在未来,它也会不断地更新迭代.所以如果你想要更准确地了解 Qt,应该通过以下几种方法: ① 官方介绍 根据官方 ...
- 2.7K star!这个汉字工具库让中文处理变得超简单,开发者必备!
嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 cnchar 是一个功能全面的汉字工具库,提供拼音转换.笔画动画.偏旁查询.成语接龙.语音合 ...
- NOIP集训 P11071 「QMSOI R1」 Distorted Fate 题解
对本题的评价:有思维含量的线段树好题.曲子好听,曲绘好看,曲师人品好,谱子写得好,鸠好看 题解: P11071 「QMSOI R1」 Distorted Fate 给定一个长度为 \(n\) 的数组 ...
- 基于Jetson Nano与PyTorch的无人机实时目标跟踪系统搭建指南
引言:边缘计算赋能智能监控 在AIoT时代,将深度学习模型部署到嵌入式设备已成为行业刚需.本文将手把手指导读者在NVIDIA Jetson Nano(4GB版本)开发板上,构建基于YOLOv5+SOR ...
- numpy.ndarray.transpose用法理解
numpy.ndarray.transpose方法对于高维数组来讲,略微有点不太好理解.下面给出我自己对该方法的理解. 对于一个高维数组,transpose((i,j,k))可以这样理解:选取原数组的 ...
- c语言笔记(翁凯男神
哼,要记得好好学习去泡帅哥吖 一.快速入门 %p 输出地址 #include <stdio.h> void f(int *p); int main(){ int i = 1; printf ...
- 第2讲、从启动到表单加载:Odoo 18 的完整执行流程详解
了解 Odoo 在从启动到用户打开一个模型表单视图时,内部到底发生了什么,是模块开发.性能调优和故障排查的关键.本文将为你系统梳理 Odoo 18 的执行流程与关键方法调用链,适用于开发者与技术架构师 ...