Node.js入门教程(十一):异步编程

一、为什么需要异步编程?

在传统的同步(Synchronous)编程中,代码是按顺序执行的,每一行代码必须等待前一行代码执行完成后才能运行。而异步编程(Asynchronous)则允许代码在等待某些操作(如 I/O 操作)完成的同时继续执行其他任务,不会阻塞程序的运行。

Node.js 采用异步 I/O 模型,这使得它特别适合处理高并发的网络应用。理解异步编程是掌握 Node.js 的关键。

Node.js 为什么需要异步编程

Node.js 是单线程的,这意味着它一次只能执行一个任务。如果使用同步编程方式,当一个耗时操作(如读取大文件或网络请求)执行时,整个程序会被阻塞,无法处理其他请求。

异步编程解决了这个问题:

  • 提高应用程序的吞吐量

  • 更有效地利用系统资源

  • 提供更好的用户体验

二、Node.js 异步编程的三种主要方式

1、回调函数(Callbacks)

回调函数是 Node.js 中最基础的异步处理方式。它通过将一个函数作为参数传递给另一个函数,在操作完成后调用这个函数。

复制代码
const fs = require('fs');

// 异步读取文件
fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('读取文件出错:', err);
        return;
    }
    console.log('文件内容:', data);
});

console.log('读取文件请求已发送,继续执行其他代码...');

特点:

  • 简单直接

  • 容易导致"回调地狱"(Callback Hell)

  • 错误处理不够直观

2、Promise

Promise 是 ES6 引入的异步编程解决方案,它表示一个异步操作的最终完成或失败及其结果值。

复制代码
const fs = require('fs').promises;

// 使用 Promise 读取文件
fs.readFile('example.txt', 'utf8')
    .then(data => {
        console.log('文件内容:', data);
    })
    .catch(err => {
        console.error('读取文件出错:', err);
    });

console.log('读取文件请求已发送,继续执行其他代码...');

特点:

  • 链式调用,避免回调地狱

  • 更好的错误处理机制

  • 状态不可逆(pending → fulfilled 或 rejected)

3、async/await

async/await 是 ES8 引入的语法糖,基于 Promise,使异步代码看起来像同步代码。

复制代码
const fs = require('fs').promises;

async function readFile() {
    try {
        const data = await fs.readFile('example.txt', 'utf8');
        console.log('文件内容:', data);
    } catch (err) {
        console.error('读取文件出错:', err);
    }
}

readFile();
console.log('读取文件请求已发送,继续执行其他代码...');

特点:

  • 代码更简洁,更易读

  • 错误处理使用 try/catch 结构

  • 必须用在 async 函数中

三、错误处理最佳实践

回调函数中的错误处理

复制代码
fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('读取文件出错:', err);
        return; // 重要:处理错误后返回,避免执行后续代码
    }
    console.log('文件内容:', data);
});

Promise 的错误处理

复制代码
fs.readFile('example.txt', 'utf8')
    .then(data => {
        console.log('文件内容:', data);
        return someOtherAsyncOperation(data);
    })
    .then(result => {
        console.log('第二步结果:', result);
    })
    .catch(err => {
        console.error('处理过程中出错:', err);
    });

async/await 的错误处理

复制代码
async function processData() {
    try {
        const data = await fs.readFile('example.txt', 'utf8');
        const result = await someOtherAsyncOperation(data);
        console.log('最终结果:', result);
    } catch (err) {
        console.error('处理过程中出错:', err);
    }
}

processData();

四、性能考虑

避免阻塞事件循环

长时间运行的同步代码会阻塞整个应用:

复制代码
// 错误示例:同步读取大文件会阻塞事件循环
const data = fs.readFileSync('largefile.txt', 'utf8');
// 其他请求需要等待这个操作完成后才能被处理

合理使用异步

不是所有操作都需要异步,简单的计算任务使用同步方式可能更高效。

复制代码
// 简单的同步计算更适合用同步方式
const sum = a + b + c;

// I/O 密集型操作使用异步
fs.readFile('data.txt', 'utf8', callback);

控制并发量

过多的并行异步操作可能导致资源耗尽。可以使用控制并发量的库(如 p-limit)或手动控制。

使用流处理大文件

对于大文件,使用流(Stream)而不是一次性读取:

复制代码
const fs = require('fs');

// 使用流处理大文件
const stream = fs.createReadStream('largefile.txt', 'utf8');
stream.on('data', (chunk) => {
    // 处理数据块
    console.log('接收到数据块:', chunk.length);
});
stream.on('end', () => {
    console.log('文件读取完成');
});

五、三种方式对比

方式 优点 缺点 适用场景
回调函数 简单直接、兼容性好 回调地狱、错误处理不直观 简单异步操作、遗留代码
Promise 链式调用、错误处理好 语法稍复杂 复杂异步流程
async/await 代码简洁、可读性强 必须用在 async 函数中 推荐方式,现代开发首选

六、本章小结

知识点 说明
异步编程的必要性 避免阻塞事件循环,提高吞吐量
回调函数 Node.js 最基础的异步处理方式
Promise ES6 引入,解决回调地狱问题
async/await ES8 语法糖,使异步代码像同步代码
错误处理 回调用条件判断,Promise 用 catch,async/await 用 try/catch
性能建议 避免阻塞事件循环,使用流处理大文件

提示 :在现代 Node.js 开发中,建议优先使用 async/await 方式编写异步代码,它让异步逻辑更清晰、更易于维护。同时,要注意正确处理错误,避免未处理的 Promise 拒绝导致应用崩溃。

相关推荐
Sca_杰2 小时前
微信客服API对接速通
javascript·node.js
FungLeo6 小时前
Node 后端实战 · D1 那些坑:100 参数上限逼出的批量写入重构
node.js·serverless·d1 数据库·sqli
谦卑a7 小时前
nodejs安装保姆级教程
前端·javascript·node.js·个人开发
烂蜻蜓8 小时前
Node.js入门教程(十八):Stream(流)
node.js
东方小月1 天前
从零开发一个 Coding Agent(九):实现 Agent 的工具调用闭环
人工智能·前端框架·node.js
Yolanda_20221 天前
nvm安装Node卡顿解决方案:两行配置切换国内镜像源
node.js
王八八。2 天前
nvm 安装与配置完全指南:从零到多版本 Node.js 自由切换
node.js
紫禁玄科2 天前
Shai-Hulud:npm生态的自我复制蠕虫风暴
前端·npm·node.js
烂蜻蜓2 天前
Node.js入门教程(十四):async/await
node.js