当前位置:首页 > 技术 > 正文内容

Node.js 异步编程:回调函数与 Promise 对象

访客 技术 1

Node.js 中异步代码的必要性

Node.js 采用非阻塞、单线程架构设计,具备以下特点:

  • 能够同时处理多个任务
  • 不会等待耗时操作(如文件读取、数据库查询、API 调用)

设想一个场景:

如果 Node.js 在每次文件读取完成前都暂停执行,那么服务器会在每个请求时冻结。

因此,Node.js 采用异步编程模式

  • 启动任务
  • 立即继续执行其他工作
  • 稍后处理结果

文件读取实例对比

假设需要读取文件并输出内容。

同步方式(阻塞式)

const content = fs.readFileSync("document.txt", "utf-8");
console.log(content);

执行过程: Node.js 暂停所有操作直到文件读取完毕。

异步方式(非阻塞式)

fs.readFile("document.txt", "utf-8", (error, content) => {
  if (error) throw error;
  console.log(content);
});

Node.js 执行流程:

  1. 开始读取文件
  2. 继续处理其他任务
  3. 完成后执行回调函数

基于回调的异步执行

回调函数是作为参数传递给另一个函数,并在后续执行的函数。

示例代码

console.log("程序开始");

fs.readFile("document.txt", "utf-8", (error, content) => {
  console.log("文件读取完成");
});

console.log("程序结束");

执行顺序:

  1. "程序开始"
  2. 文件读取启动(后台进行)
  3. "程序结束"
  4. 文件读取完成 → 回调函数执行 → "文件读取完成"

这就是非阻塞行为

嵌套回调的问题

考虑多个异步操作的情况

fs.readFile("file-a.txt", "utf-8", (error, contentA) => {
  fs.readFile("file-b.txt", "utf-8", (error, contentB) => {
    fs.readFile("file-c.txt", "utf-8", (error, contentC) => {
      console.log(contentA, contentB, contentC);
    });
  });
});

存在问题:

  • 难以阅读(金字塔结构)
  • 错误处理复杂
  • 调试困难

这种情况被称为:回调地狱

基于 Promise 的异步处理

Promise 的引入解决了回调函数的问题。

相同示例的 Promise 实现:

const fileSystem = require("fs").promises;

fileSystem.readFile("file-a.txt", "utf-8")
  .then((contentA) => {
    return fileSystem.readFile("file-b.txt", "utf-8");
  })
  .then((contentB) => {
    return fileSystem.readFile("file-c.txt", "utf-8");
  })
  .then((contentC) => {
    console.log(contentC);
  })
  .catch((error) => {
    console.error(error);
  });

Promise 生命周期

Promise 具有三种状态:

  1. 待定状态(Pending) → 初始状态
  2. 已解决状态(Fulfilled) → 成功
  3. 已拒绝状态(Rejected) → 失败

Promise 的优势

1. 更好的可读性

避免深度嵌套

2. 统一错误处理

.catch(error => console.error(error));

3. 链式调用

可以清晰地连接异步操作

4. 为 async/await 提供基础

现代 JavaScript 在 Promise 基础上构建

回调与 Promise 可读性对比

回调风格

doFirstOperation((resultOne) => {
  doSecondOperation(resultOne, (resultTwo) => {
    doThirdOperation(resultTwo, (resultThree) => {
      console.log(resultThree);
    });
  });
});

Promise 风格

doFirstOperation()
  .then(doSecondOperation)
  .then(doThirdOperation)
  .then(console.log)
  .catch(console.error);

主要差异

特性 回调函数 Promise
可读性 差(嵌套结构) 清晰(线性结构)
错误处理 分散 集中统一
调试难度 困难 相对容易
可扩展性

回调执行链

开始
  |
读取文件()
  |
回调函数 ---> 读取文件()
           |
           回调函数 ---> 读取文件()
                      |
                      回调函数 ---> 结果

Promise 流程

Promise(待定)
        |
   已解决 ---> then()
        |
   已拒绝 ---> catch()

最佳实践建议

  • 避免在复杂流程中使用回调
  • 使用Promiseasync/await
  • 始终处理错误(catch
  • 优先选择:
async/await

获得更清洁的代码结构

回调函数为 Node.js 带来了异步能力,但扩展性不佳。

Promise 带来了结构化解决方案。

如今,async/await 让异步代码感觉像同步代码一样,同时保持性能优势。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。