第三课:异步编程核心:Callback、Promise与Async/Await
Node.js 是一个基于事件驱动的非阻塞 I/O 模型,这使得它非常适合处理高并发的网络请求。在 Node.js 中,异步编程是一项非常重要的技能。理解和掌握异步编程的不同方式不仅能提高代码的效率,还能让你更好地应对复杂的开发任务。本文将深入探讨 Node.js 中常见的三种异步编程方式:回调函数(Callback)、Promise 和 async/await。通过比较它们的用法和特点,我们能够选择最适合的方式来处理异步任务,并解决其中可能遇到的问题。

1. 单线程与事件循环机制解析
1.1 单线程模型
Node.js 采用单线程模型,这意味着它一次只能执行一个任务。这种设计简化了多线程环境下的复杂问题,如资源竞争和死锁等。然而,单线程也意味着 Node.js 无法利用多核 CPU 的优势。为了解决这个问题,Node.js 使用了事件驱动和非阻塞 I/O 模型。
1.2 事件循环机制
事件循环是 Node.js 处理异步操作的关键机制。它允许 Node.js 在等待 I/O 操作完成的同时继续执行其他任务。事件循环的工作流程大致如下:
- 执行同步代码:执行栈中的同步代码。
- 处理异步任务:将异步任务(如 I/O 操作、定时器等)放入相应的队列(宏任务队列或微任务队列)。
- 执行微任务:在当前执行栈为空时,执行微任务队列中的所有任务。
- 执行宏任务:执行一个宏任务,然后再次清空微任务队列,如此循环。
console.log('Start');
setTimeout(() => {console.log('setTimeout');
}, 0);
Promise.resolve().then(() => {console.log('Promise');
});
process.nextTick(() => {console.log('nextTick');
});
console.log('End');// 输出顺序: Start End nextTick Promise setTimeout
2. 回调地狱问题与解决方案
2.1 回调地狱问题
回调函数是最早在 JavaScript 中用于处理异步操作的一种方式。然而,当需要处理多个异步操作时,回调函数可能会导致“回调地狱”(callback hell)。多个嵌套的回调函数会使代码层级深、逻辑混乱,难以维护。
const fs = require('fs')
fs.readFile('file1.txt', 'utf8', (err, data1) => {if (err) throw err;fs.readFile('file2.txt', 'utf8', (err, data2) => {if (err) throw err;fs.readFile('file3.txt', 'utf8', (err, data3) => {if (err) throw err;console.log(data1, data2, data3);});});
});
2.2 解决方案
2.2.1 使用Promise
Promise 是为了解决回调地狱问题而引入的。它表示一个异步操作的最终完成(或失败)及其结果值。通过链式调用 .then() 方法,可以清晰地组织异步代码。
const fs = require('fs').promises;fs.readFile('file1.txt', 'utf8').then((data1) => {return fs.readFile('file2.txt', 'utf8');}).then((data2) => {return fs.readFile('file3.txt', 'utf8');}).then((data3) => {console.log(data1, data2, data3);}).catch((err) => {console.error('Error:', err);});
2.2.2 使用async/await
async/await 是基于 Promise 的语法糖,它让异步代码看起来更像同步代码,从而提高可读性。
const fs = require('fs').promises;async function readFiles() {try {const data1 = await fs.readFile('file1.txt', 'utf8');const data2 = await fs.readFile('file2.txt', 'utf8');const data3 = await fs.readFile('file3.txt', 'utf8');console.log(data1, data2, data3);} catch (err) {console.error('Error:', err);}
}readFiles();
3. Promise链式调用与错误处理
3.1 Promise链式调用
Promise 允许我们进行链式调用,每一个 .then() 方法都会返回一个新的 Promise,这样就可以继续处理异步操作。
function getDataFromAPI() {return new Promise((resolve, reject) => {setTimeout(() => {const data = { id: 1, name: 'Alice' };resolve(data);}, 500);});
}function processData(data) {return new Promise((resolve, reject) => {setTimeout(() => {const processedData = { ...data, role: 'Admin' };resolve(processedData);}, 500);});
}function saveData(data) {return new Promise((resolve, reject) => {setTimeout(() => {console.log('Data saved:', data);resolve();}, 500);});
}getDataFromAPI().then(data => processData(data)).then(processedData => saveData(processedData)).catch(error => console.error('Error:', error));
3.2 Promise错误处理
Promise 使用 .catch() 来捕获任何在 .then() 中发生的错误。
Promise.resolve().then(() => {throw new Error('Something went wrong');}).then(() => {console.log('This will not be executed');}).catch((err) => {console.error('Error caught:', err);});
4. Async/Await语法糖实战
4.1 async/await基本用法
async/await 是基于 Promise 的语法糖,它让异步代码看起来更像同步代码。async 修饰函数,表示该函数会返回一个 Promise,而 await 用于等待一个 Promise 完成。
const fs = require('fs').promises;async function readFiles() {try {const data1 = await fs.readFile('file1.txt', 'utf8');const data2 = await fs.readFile('file2.txt', 'utf8');console.log('All files content:', data1, data2);} catch (err) {console.error('Error:', err);}
}readFiles();
4.2 并行与串行执行
使用 Promise.all() 可以并行执行多个异步操作,而使用 await 则会串行执行。
const fs = require('fs').promises;async function readFiles() {try {const [data1, data2] = await Promise.all([fs.readFile('file1.txt', 'utf8'),fs.readFile('file2.txt', 'utf8')]);console.log('All files content:', data1, data2);} catch (err) {console.error('Error:', err);}
}readFiles();
串行执行示例:
const fs = require('fs').promises;async function readFiles() {try {const data1 = await fs.readFile('file1.txt', 'utf8');const data2 = await fs.readFile('file2.txt', 'utf8');console.log('All files content:', data1, data2);} catch (err) {console.error('Error:', err);}
}readFiles();
总结
Node.js 提供了多种处理异步操作的方式,包括回调函数、Promise 和 async/await。每种方式都有其适用场景和优缺点:
- 回调函数:简单直接,但容易导致回调地狱,难以维护。
- Promise:提供了链式调用和错误处理机制,解决了回调地狱的问题,但仍有一定的复杂性。
- async/await:基于 Promise,简洁、易读,是现代 JavaScript 的推荐写法。
根据不同的需求和代码结构,你可以选择最适合的异步编程方式来提高代码的可读性、可维护性和性能。
关注我!!🫵 持续为你带来Python爬虫相关内容。
相关文章:
第三课:异步编程核心:Callback、Promise与Async/Await
Node.js 是一个基于事件驱动的非阻塞 I/O 模型,这使得它非常适合处理高并发的网络请求。在 Node.js 中,异步编程是一项非常重要的技能。理解和掌握异步编程的不同方式不仅能提高代码的效率,还能让你更好地应对复杂的开发任务。本文将深入探讨…...
红果短剧安卓+IOS双端源码,专业短剧开发公司
给大家拆解一下红果短剧/河马短剧,这种看光解锁视频,可以挣金币的短剧APP。给大家分享一个相似的短剧APP源码,这个系统已接入穿山甲广告、百度广告、快手广告、腾讯广告等,类似红果短剧的玩法,可以看剧赚钱,…...
C# ArrayPool
ArrayPool<T> 的作用ArrayPool<T> 的使用方式共享数组池自定义数组池 注意事项应用场景 在C#中,ArrayPool<T> 是一个非常有用的工具类,主要用于高效地管理数组的分配和回收,以减少内存分配和垃圾回收的压力。它属于 System…...
Conda 生态系统介绍
引言 Conda 是一个开源的包管理和环境管理系统,最初由 Continuum Analytics 开发,现为 Anaconda 公司维护。它在数据科学和 Python/R 生态中占据核心地位,因其能跨平台(Linux/Windows/macOS)管理依赖关系,并通过虚拟环境隔离不同项目的开发环境。Conda 的生态系统包含多…...
批量将 Word 拆分成多个文件
当一个 Word 文档太大的时候,我们通常会将一个大的 Word 文档拆分成多个小的 Word 文档,在 Office 中拆分 Word 文档是比较麻烦的,我们需要将 Word 文档的页面复制到另外一个 Word 文档中去,然后删除原 Word 文档中的内容。当然也…...
Gravitino源码分析-SparkConnector 实现原理
Gravitino SparkConnector 实现原理 本文参考了官网介绍,想看官方解析请参考 官网地址 本文仅仅介绍原理 文章目录 Gravitino SparkConnector 实现原理背景知识-Spark Plugin 介绍(1) **插件加载**(2) **DriverPlugin 初始化**(3) **ExecutorPlugin 初始化**(4) *…...
react基本功
useLayoutEffect useLayoutEffect 用于在浏览器重新绘制屏幕之前同步执行代码。它与 useEffect 相同,但执行时机不同。 主要特点 执行时机:useLayoutEffect 在 DOM 更新完成后同步执行,但在浏览器绘制之前。这使得它可以在浏览器渲染之前读取和修改 DOM,避免视觉上的闪烁…...
python-leetcode-解决智力问题
2140. 解决智力问题 - 力扣(LeetCode) 这道题是一个典型的 动态规划(Dynamic Programming, DP) 问题,可以使用 自底向上 的方式解决。 思路 定义状态: 设 dp[i] 表示从第 i 题开始,能获得的最高…...
引领变革!北京爱悦诗科技有限公司荣获“GAS消费电子科创奖-产品创新奖”!
在2025年“GAS消费电子科创奖”评选中,北京爱悦诗科技有限公司提交的“aigo爱国者GS06”,在技术创新性、设计创新性、工艺创新性、智能化创新性及原创性五大维度均获得评委的高度认可,荣获“产品创新奖”。 这一奖项不仅是对爱悦诗在消费电子…...
微信小程序+SpringBoot的单词学习小程序平台(程序+论文+讲解+安装+修改+售后)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统背景 (一)社会需求背景 在全球化的大背景下,英语作为国际…...
wordpress分类名称调用的几种情况
在WordPress中,如果你想调用当前分类的名称,可以使用single_cat_title()函数。以下是一些常见的使用方法和场景: 1. 在分类页面调用当前分类名称 如果你正在分类存档页面(category.php)中,可以直接使用single_cat_title()函数来…...
HMC7043和HMC7044芯片配置使用
一,HMC7043芯片 MC7043独特的特性是对14个通道分别进行独立灵活的相位管理。所有14个通道均支持频率和相位调整。这些输出还可针对50 Ω或100 Ω内部和外部端接选项进行编程。HMC7043器件具有RF SYNC功能,支持确定性同步多个HMC7043器件,即确保所有时钟输出从同一时钟沿开始…...
html播放本地音乐
本地有多个音乐文件,想用 html 逐个播放,或循环播放,并设置初始音量。 audio 在 html 中播放音乐文件用 audio 标签: controls 启用控制按钮,如进度条、播放、音量、速度等。不加不显示任何 widget。autoplay 理应启…...
Windows11下玩转 Docker
一、前提准备 WSL2:Windows 提供的一种轻量级 Linux 运行环境,具备完整的 Linux 内核,并支持更好的文件系统性能和兼容性。它允许用户在 Windows 系统中运行 Linux 命令行工具和应用程序,而无需安装虚拟机或双系统。Ubuntu 1.1 安…...
vLLM + Open-WebUI 本地私有化部署 DeepSeek-R1-Distill-Qwen-32B 方案
一、vLLM 部署 DeepSeek-R1-Distill-Qwen-32B DeepSeek-R1-Distill 系列模型是 DeepSeek-R1 的蒸馏模型,官方提供了从 1.5B - 70B 不同尺寸大小的模型。特别适合在计算资源有限的环境中部署。 DeepSeek-R1 各个版本的蒸馏模型评估结果如下: 其中 DeepS…...
【基础知识】回头看Maven基础
背景 项目过程中,对于Maven的pom.xml文件,很多时候,我通过各种参考、仿写,最终做出想要的效果。 但实际心里有些迷糊,不清楚具体哪个基础的配置所实现的效果。 今天,特意回过头来,了解Maven的基…...
在 MyBatis 中,若数据库字段名与 SQL 保留字冲突解决办法
在 MyBatis 中,若数据库字段名与 SQL 保留字冲突,可通过以下方法解决: 目录 一、使用转义符号包裹字段名二、通过别名映射三、借助 MyBatis-Plus 注解四、全局配置策略(辅助方案)最佳实践与注意事项 一、使用转义符号…...
docker-compose Install reranker(fastgpt支持) GPU模式
前言BGE-重新排名器 与 embedding 模型不同,reranker 或 cross-encoder 使用 question 和 document 作为输入,直接输出相似性而不是 embedding。 为了平衡准确性和时间成本,cross-encoder 被广泛用于对其他简单模型检索到的前 k 个文档进行重新排序。 例如,使用 bge 嵌入模…...
200W数据需要去重,如何优化?
优化去重逻辑的时间取决于多个因素,包括数据量、数据结构、硬件性能(CPU、内存)、去重算法的实现方式等。以下是对优化去重逻辑的详细分析和预期优化效果: 1. 去重逻辑的性能瓶颈 时间复杂度:使用HashSet去重的时间复…...
使用免费IP数据库离线查询IP归属地
一、准备工作 1.下载免费IP数据库 首先,访问 MaxMind官网(https://www.maxmind.com/en/home)如果你还没有MaxMind账号,可以通过此链接地址(https://www.maxmind.com/en/geolite2/signup)进行账号注册&…...
stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
