Promise链式调用、async和await
目录
回调函数地狱与Promise链式调用
一、回调函数地狱
1. 典型场景示例
2. 回调地狱的问题
二、Promise链式调用
1. 链式调用解决回调地狱
2. 链式调用的核心规则
三、链式调用深度解析
1. 链式调用本质
2. 错误处理机制
四、回调地狱 vs 链式调用
五、高级链式技巧
1. 条件分支
2. 并行任务
3. 链式中断
六、总结
async 和 await
一、async 函数
二、await 表达式
三、async/await解决回调地狱
四、高级用法
1. 并行执行异步任务
2. 循环中的 await
3. 顶层 await
五、常见问题与解决方案
六、链式调用 vs async/await
七、总结
回调函数地狱与Promise链式调用
一、回调函数地狱
回调函数地狱是 JavaScript 异步编程早期面临的典型问题,表现为多层嵌套的回调函数,导致代码难以阅读和维护。
1. 典型场景示例
需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中。
使用回调函数实现:
// 1. 获取默认第一个省份的名字
axios({ url: 'http://ajax.net/api/province' }).then(result => {const pname = result.data.list[0]document.querySelector('.province').innerHTML = pname// 2. 获取默认第一个城市的名字axios({ url: 'http://ajax.net/api/city', params: { pname } }).then(result => {const cname = result.data.list[0]document.querySelector('.city').innerHTML = cname// 3. 获取默认第一个地区的名字axios({ url: 'http://ajax.net/api/area', params: { pname, cname } }).then(result => {console.log(result)const areaName = result.data.list[0]document.querySelector('.area').innerHTML = areaName})})})
在回调函数中嵌套回调函数,一直嵌套下去就形成了回调函数地狱。
2. 回调地狱的问题
-
代码金字塔:嵌套层级深,形成“向右倾倒”的金字塔结构,可读性差。
-
错误处理冗余:每个回调需单独处理错误,代码重复。
-
流程控制困难:难以实现复杂逻辑(如并行任务、条件分支),耦合性严重。
二、Promise链式调用
Promise 通过链式调用(Chaining)解决了回调地狱问题,将嵌套结构转为扁平化的流水线式代码。
链式调用:利用 then 方法返回新 Promise 对象特性,一直串联下去。
1. 链式调用解决回调地狱
将上述回调地狱改写为链式调用:
let pname = ''
// 1. 得到-获取省份Promise对象
axios({ url: 'http://hmajax.itheima.net/api/province' }).then(result => {pname = result.data.list[0]document.querySelector('.province').textContent = pname// 2. 得到-获取城市Promise对象return axios({ url: 'http://hmajax.itheima.net/api/city', params: { pname } })}).then(result => {const cname = result.data.list[0]document.querySelector('.city').textContent = cname// 3. 得到-获取地区Promise对象return axios({ url: 'http://hmajax.itheima.net/api/area', params: { pname, cname } })}).then(result => {const aname = result.data.list[0]document.querySelector('.area').textContent = aname}).catch(error => {console.log(error)})
Promise 链式调用如何解决回调函数地狱?
- then 的回调函数中 return Promise对象,影响当前新 Promise 对象的值。
2. 链式调用的核心规则
-
值传递:每个
.then()接收前一个 Promise 的结果。 -
返回新 Promise:
.then()回调中可返回新 Promise,继续链式调用。 -
错误冒泡:链中任何位置的错误都会传递到最近的
.catch()。
三、链式调用深度解析
1. 链式调用本质
每个 .then() 会返回 新的 Promise 对象,其状态由回调函数决定:
-
若回调返回非 Promise 值 → 新 Promise 直接成功(
Fulfilled)。
Promise.resolve(1).then(n => n + 2) // 返回 3(普通值).then(console.log); // 输出 3
-
若回调返回 Promise → 新 Promise 与其状态同步。
Promise.resolve(1).then(n => Promise.resolve(n + 2)) // 返回新 Promise.then(console.log); // 输出 3
-
若回调抛出错误 → 新 Promise 失败(
Rejected)。
Promise.resolve(1).then(() => { throw new Error('Fail') }).catch(console.error); // 捕获错误
在 then 回调函数中,return 的值会传给 then 方法生成的新 Promise 对象。
2. 错误处理机制
-
统一捕获:通过一个
.catch()捕获链中所有错误。 -
中断链式:一旦触发错误,后续
.then()会被跳过,直接跳转至.catch()。 -
恢复链式:在
.catch()后仍可继续.then()。
四、回调地狱 vs 链式调用
| 特性 | 回调函数 | Promise 链式调用 |
|---|---|---|
| 代码结构 | 嵌套层级深,可读性差 | 扁平化链式,逻辑清晰 |
| 错误处理 | 每个回调单独处理,冗余 | 统一通过 .catch() 捕获 |
| 流程控制 | 难以实现复杂逻辑(如并行、条件分支) | 结合 Promise.all、async/await 更灵活 |
| 调试难度 | 堆栈信息不完整,难以追踪 | 错误冒泡机制,堆栈更清晰 |
| 复用性 | 回调函数耦合度高,复用困难 | 每个 .then() 可独立封装,复用性强 |
五、高级链式技巧
1. 条件分支
fetchUser().then(user => {if (user.isVIP) {return fetchVIPContent(user.id); // 返回新 Promise} else {return fetchBasicContent(); // 返回普通值}}).then(content => {console.log('内容:', content);});
2. 并行任务
结合 Promise.all 实现并行:
const fetchUser = axios.get('/api/user');
const fetchPosts = axios.get('/api/posts');Promise.all([fetchUser, fetchPosts]).then(([user, posts]) => {console.log('用户:', user.data, '帖子:', posts.data);});
3. 链式中断
通过返回 Promise.reject() 主动中断链式:
login().then(token => {if (!tokenValid(token)) {return Promise.reject(new Error('Token 无效')); // 主动中断}return getUserInfo(token);}).catch(error => {console.error('流程中断:', error);});
六、总结
-
回调地狱是早期异步编程的痛点,代码臃肿且难以维护。
-
Promise 链式调用通过扁平化结构和错误冒泡机制,极大提升了代码可读性和可维护性。
-
最佳实践:
-
优先使用 Promise 链式替代嵌套回调。
-
结合
async/await语法糖进一步简化异步代码。 -
善用
Promise.all、Promise.race等工具处理复杂场景。
-
async 和 await
async/await是 JavaScript 处理异步操作的语法糖,基于 Promise 实现,旨在让异步代码的写法更接近同步逻辑,彻底解决回调地狱问题。概念: 在 async 函数内,使用 await 关键字取代 then 函数,等待获取 Promise 对象状态的结果值 。
一、async 函数
-
定义与特性
-
语法:在函数前添加
async关键字,表示该函数包含异步操作。 -
返回值:始终返回一个 Promise 对象:
-
若函数返回非 Promise 值,会自动包装为
Promise.resolve(value)。 -
若抛出错误,返回
Promise.reject(error)。
-
async function fetchData() {return 'Hello World'; // 等价于 Promise.resolve('Hello World') } fetchData().then(console.log); // 输出 "Hello World" -
-
错误处理
-
在
async函数内部使用try/catch捕获同步或异步错误。
async function fetchWithError() {try {const data = await axios({ url: 'invalid-url' });} catch (error) {console.error('捕获错误:', error); // 网络错误或 Promise 拒绝} } fetchWithError() -
二、await 表达式
-
作用与规则
-
语法:
await后接一个 Promise 对象(或原始值)。 -
行为:
-
暂停当前
async函数的执行,等待 Promise 完成。 -
若 Promise 成功,返回其解决的值。
-
若 Promise 拒绝,抛出拒绝的原因(需用
try/catch捕获)。
-
-
限制:
await只能在async函数内部使用。
async function getUser() {const response = await fetch('/api/user'); // 等待 fetch 完成const data = await response.json(); // 等待 JSON 解析return data; } -
-
执行顺序
-
同步代码优先:
await不会阻塞函数外的代码。
async function demo() {console.log(1);await Promise.resolve(); // 暂停此处,但外部代码继续执行console.log(2); } demo(); console.log(3); // 输出顺序: 1 → 3 → 2 -
三、async/await解决回调地狱
将上述回调地狱改写为async/await:
async function getData() {try {const pObj = await axios({ url: 'http://ajax.net/api/province' })const pname = pObj.data.list[0]const cObj = await axios({ url: 'http://ajax.net/api/city', params: { pname } })const cname = cObj.data.list[0]const aObj = await axios({ url: 'http://ajax.net/api/area', params: { pname, cname } })const aname = aObj.data.list[0]document.querySelector('.province').innerHTML = pnamedocument.querySelector('.city').innerHTML = cnamedocument.querySelector('.area').innerHTML = aname} catch (error) {console.log(error)}
}
getData()
错误处理:
-
Promise:依赖
.catch()或.then()的第二个参数。 -
async/await:使用
try/catch统一处理同步和异步错误。
四、高级用法
1. 并行执行异步任务
-
顺序执行(效率低):
const user = await fetchUser(); // 先执行 const posts = await fetchPosts(); // 后执行(等待 user 完成) -
并行执行(效率高):
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
2. 循环中的 await
-
错误示例(顺序执行,耗时长):
for (const url of urls) {await fetch(url); // 每个请求等待上一个完成 } -
正确示例(并行触发):
const promises = urls.map(url => fetch(url)); const results = await Promise.all(promises);
3. 顶层 await
-
ES2022+ 支持在模块的顶层作用域使用
await。// 模块中直接使用 const data = await fetchData(); console.log(data);
五、常见问题与解决方案
-
忘记 await
-
现象:函数返回 Promise 而非预期值。
-
解决:确保异步操作前添加
await。
async function demo() {const data = fetch('/api'); // 错误!缺少 awaitconsole.log(data); // 输出 Promise 对象 } -
-
未捕获的错误
-
现象:未使用
try/catch导致未处理的 Promise 拒绝。 -
解决:始终用
try/catch包裹await,或在函数调用后加.catch()。
async function riskyTask() {await dangerousOperation(); } riskyTask().catch(console.error); // 捕获未处理的错误 -
-
性能陷阱
-
现象:不必要的顺序执行降低性能。
-
解决:合理使用
Promise.all或Promise.race优化。
-
六、链式调用 vs async/await
| 特性 | Promise 链式调用 | async/await |
|---|---|---|
| 代码结构 | 链式 .then(),需处理嵌套 | 类似同步代码,无嵌套 |
| 错误处理 | 通过 .catch() 或链式参数 | 使用 try/catch 统一处理 |
| 底层机制 | 直接操作 Promise 链 | 基于生成器和 Promise 的语法糖 |
| 可读性 | 简单链式清晰,复杂场景混乱 | 逻辑直观,适合复杂异步流程 |
| 调试体验 | 错误堆栈可能跨多个 .then() | 错误堆栈更贴近代码行号 |
七、总结
-
核心优势:
-
代码扁平化,更接近同步逻辑的直观性。
-
错误处理更统一(
try/catch覆盖同步和异步错误)。
-
-
适用场景:
-
需要顺序执行的异步任务(如依次请求 A → B → C)。
-
复杂异步流程(需结合条件判断、循环等)。
-
-
注意事项:
-
避免滥用导致性能问题(如无必要的顺序执行)。
-
在非模块环境中,顶层
await需封装在async函数中。
-
相关文章:
Promise链式调用、async和await
目录 回调函数地狱与Promise链式调用 一、回调函数地狱 1. 典型场景示例 2. 回调地狱的问题 二、Promise链式调用 1. 链式调用解决回调地狱 2. 链式调用的核心规则 三、链式调用深度解析 1. 链式调用本质 2. 错误处理机制 四、回调地狱 vs 链式调用 五、高级链式技…...
React ROUTER之嵌套路由
第一张是需要修改router文件createBrowserRouterd参数数组中的路由关系 第二张是需要在一级路由的index.js中选择二级路由的位置 第一步是在全局的router.js文件中加入新的children属性,如图 第二步是在一级路由的index.js文件中声明outLet组件 默认二级路由 在…...
TestNG 单元测试详解
1、测试环境 jdk1.8.0 121 myeclipse-10.0-offline-installer-windows.exe TestNG 插件 org.testng.eclipse 6.8.6.20130607 0745 2、介绍 套件(suite):由一个 XML 文件表示,通过<suite>标签定义,包含一个或更多测试(test)。测试(test):由<test>定义…...
测试100问:http和https的区别是什么?
哈喽,大家好,我是十二,今天给大家分享的问题是:http和https的区别是什么? 首先我们要知道 HTTP 协议传播的数据都是未加密的,也就是明文的,因此呢使用 http协议传输一些隐私信息也就非常不安全&…...
通过python实现bilibili缓存视频转为mp4格式
需要提前下好ffmpeg import os import fnmatch import subprocess Bilibili缓存的视频,*280.m4s结尾的是音频文件,*050.m4s结尾的是视频,删除16进制下前9个0,即为正常音/视频 使用os.walk模块,遍历每一个目录…...
高效爬虫:一文掌握 Crawlee 的详细使用(web高效抓取和浏览器自动化库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Crawlee概述1.1 Crawlee介绍1.2 为什么 Crawlee 是网页抓取和爬取的首选?1.3 为什么使用 Crawlee 而不是 Scrapy1.4 Crawlee的安装二、Crawlee的基本使用2.1 BeautifulSoupCrawler的使用方式2.2 ParselCrawler的使…...
React中 点击事件写法 的注意(this、箭头函数)
目录 1、错误写法:onClick{this.acceptAlls()} 2、正确写法:onClick{this.acceptAlls}(不带括号) 总结 方案1:构造函数绑定 方案2:箭头函数包装方法(更简洁) 方案3&am…...
【分享】Ftrans文件摆渡系统:既保障传输安全,又提供强集成支持
【分享】Ftrans文件摆渡系统:既保障传输安全,又提供强集成支持! 在数字化浪潮中,企业对数据安全愈发重视,网络隔离成为保护核心数据的关键防线,比如隔离成研发网-办公网、生产网-测试网、内网-外网等。网络…...
Day31笔记-进程和线程
一、进程和线程简介 1.概念 1.1多任务 程序的运行是CPU和内存协同工作的结果 操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统 问题1:什么是多任务? 就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边…...
python每日一练
题目一 输入10个整数,输出其中不同的数,即如果一个数出现了多次,只输出一次(要求按照每一个不同的数第一次出现的顺序输出)。 解题 错误题解 a list(map(int,input().split())) b [] b.append(a[i]) for i in range(2,11):if a[i] not in b:b.append(a[i]) print(b)但是会…...
深入理解 RxSwift 中的 Driver:用法与实践
目录 前言 一、什么是Driver 1.不会发出错误 2.主线程保证 3.可重放 4.易于绑定 二、Driver vs Observable 三、使用场景 1.绑定数据到UI控件 2.响应用户交互 3.需要线程安全的逻辑 4.如何使用Driver? 1.绑定文本输入到Label 2.处理按钮点击事件 3.从网络请求…...
算法思想之前缀和(二)
欢迎拜访:雾里看山-CSDN博客 本篇主题:算法思想之前缀和(二) 发布时间:2025.4.11 隶属专栏:算法 目录 算法介绍核心思想大致步骤 例题和为 K 的子数组题目链接题目描述算法思路代码实现 和可被 K 整除的子数组题目链接题目描述算法…...
C++ - 数据容器之 unordered_map(声明与初始化、插入元素、访问元素、遍历元素、删除元素、查找元素)
一、unordered_map unordered_map 是 C STL 中的一个关联容器,它有如下特点 unordered_map 存储键值对,使用哈希表实现 unordered_map 的每个键在容器中只能出现一次 unordered_map 的存储的键值对是无序的 平均情况下,查找、插入、删除都…...
六、分布式嵌入
六、分布式嵌入 文章目录 六、分布式嵌入前言一、先要配置torch.distributed环境二、Distributed Embeddings2.1 EmbeddingBagCollectionSharder2.2 ShardedEmbeddingBagCollection 三、Planner总结 前言 我们已经使用了TorchRec的主模块:EmbeddedBagCollection。我…...
硬件知识积累 单片机+ 光耦 + 继电器需要注意的地方
1. 电路图 与其数值描述 1.1 单片机引脚信号为 OPtoCoupler_control_4 PC817SB 为 光耦 继电器 SRD-05VDC-SL-A 的线圈电压为 67Ω。 2. 需注意的地方 1. 单片机的推挽输出的电流最大为 25mA 2. 注意光耦的 CTR 参数 3. 注意继电器线圈的 内阻 4. 继电器的开启电压。 因为光耦…...
Dockerfile 学习指南和简单实战
引言 Dockerfile 是一种用于定义 Docker 镜像构建步骤的文本文件。它通过一系列指令描述了如何一步步构建一个镜像,包括安装依赖、设置环境变量、复制文件等。在现实生活中,Dockerfile 的主要用途是帮助开发者快速、一致地构建和部署应用。它确保了应用…...
MCU屏和RGB屏
一、MCU屏 MCU屏:全称为单片机控制屏(Microcontroller Unit Screen),在显示屏背后集成了单片机控制器,因此,MCU屏里面有专用的驱动芯片。驱动芯片如:ILI9488、ILI9341、SSD1963等。驱动芯片里…...
Elasticsearch 向量数据库,原生支持 Google Cloud Vertex AI 平台
作者:来自 Elastic Valerio Arvizzigno Elasticsearch 将作为第一个第三方原生语义对齐引擎,支持 Google Cloud 的 Vertex AI 平台和 Google 的 Gemini 模型。这使得联合用户能够基于企业数据构建完全可定制的生成式 AI 体验,并借助 Elastics…...
蓝桥杯基础数论入门
一.试除法 首先我们要了解,所有大于1的自然数都能进行质因数分解。试除法作用如下: 质数判断 试除法通过验证一个数是否能被小于它的数(一般是用2到用根号x)整除来判断其是否为质数。根据定义,质数只能被1和自身整除…...
Spring 事件机制与观察者模式的深度解析
一、引言 在软件设计中,观察者模式(Observer Pattern)是一种非常经典且实用的设计模式。它允许一个对象(Subject)在状态发生改变时通知所有依赖它的对象(Observers),从而实现对象之…...
【软考系统架构设计师】信息安全技术基础知识点
1、 信息安全包括5个基本要素:机密性、完整性、可用性、可控性与可审查性。 机密性:确保信息不暴露给未授权的实体或进程。(采取加密措施) 完整性:只有得到允许的人才能修改数据,并且能够判断出数据是否已…...
Python编程快速上手 让繁琐工作自动化笔记
编程基础 字符串使用单引号...
2025年第十六届蓝桥杯省赛真题解析 Java B组(简单经验分享)
之前一年拿了国二后,基本就没刷过题了,实力掉了好多,这次参赛只是为了学校的加分水水而已,希望能拿个省三吧 >_< 目录 1. 逃离高塔思路代码 2. 消失的蓝宝思路代码 3. 电池分组思路代码 4. 魔法科考试思路代码 5. 爆破思路…...
Java 设计模式:策略模式详解
Java 设计模式:策略模式详解 策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端,从…...
什么是TensorFlow?
TensorFlow 是由 Google Brain 团队开发的开源机器学习框架,被广泛应用于深度学习和人工智能领域。它的基本概念包括: 1. 张量(Tensor):在 TensorFlow 中,数据以张量的形式进行处理。张量是多维数组的泛化…...
【3GPP核心网】【5G】精讲5G网络语音业务系统架构
1. 欢迎大家订阅和关注,精讲3GPP通信协议(2G/3G/4G/5G/IMS)知识点,专栏会持续更新中.....敬请期待! 目录 1. 音视频业务 2. 消息类业务 SMS over IMS SMS over NAS 3. 互联互通架构 3.1 音视频业务互通场景 3.2 5G 用户与 5G 用户互通 3.3 5G 用户与 4G 用户的互通…...
01-算法打卡-数组-二分查找-leetcode(704)-第一天
1 数组基础理论 数组是存放在连续内存空间上的相同数据结构的集合。数组可以通过下标索引快速获取数据,因为数组的存储空间是连续的所以在删除、更新数据的时候需要移动其他元素的地址。 下图是一个数组的案例图形:【内存连续、索引小标从0开始可…...
怎么看英文论文 pdf沉浸式翻译
https://arxiv.org/pdf/2105.09492 Immersive Translate Xournal打开...
Linux 进程基础(一):冯诺依曼结构
文章目录 一、冯诺依曼体系结构是什么?🧠二、冯诺依曼体系为何成为计算机组成的最终选择?(一)三大核心优势奠定主流地位(二)对比其他架构的不可替代性 三、存储分级:速度与容量的平衡…...
RabbitMQ 深度解析:从基础到高级应用的全面指南
🐰 RabbitMQ 深度解析:从基础到高级应用的全面指南 前言📘 一、RabbitMQ 简介⚙️ 二、核心特性可靠性 🔒灵活路由 🔄高可用性 🌐多协议支持 🌍多语言客户端 💻插件机制 ὐ…...
