《深入浅出 Node.js》第四章:异步编程 详细总结

第四章是第三章的"续集"和"解药"。第三章告诉你"异步为什么牛、底层怎么实现的",第四章告诉你"异步这么难写,怎么才能优雅地驾驭它"。朴灵作者把焦点从底层原理转向实践编程模型,系统介绍了从原始回调到现代 async/await 的解决方案演进路径。

这一章的核心思想:控制权反转------把"下一步干什么"交给框架/机制,而不是手动嵌套回调。

我们按小节逐一详细总结,每个细节配解释 + 多例子 + 生动比喻。

4.1 异步编程的优势与难点(第三章3.4已讲,这里略过回顾)

优势:高并发、低资源。

难点:回调地狱、异常难捕获、流程复杂。

生动比喻:回调地狱像"俄罗斯套娃",一层套一层;像"金字塔",越写越斜。

4.2 事件发布/订阅模型(EventEmitter)

核心思想

"一对多"解耦:发布者只管emit事件,订阅者只管on监听,互不干扰。

生动比喻:像广播电台(发布者)喊"新闻来了!",所有收音机(订阅者)都能收到,谁想听就调频。

API 详解

js 复制代码
const EventEmitter = require('events');
const ee = new EventEmitter();

// 订阅
ee.on('news', (data) => console.log('收到新闻:', data));     // 多次触发
ee.once('alert', () => console.log('紧急警报,只响一次'));  // 只一次

// 发布
ee.emit('news', '股市大涨');
ee.emit('news', '天气预报');
ee.emit('alert');  // 只打印一次

// 移除
const handler = () => console.log('移除我');
ee.on('remove', handler);
ee.removeListener('remove', handler);

例子1:文件处理多监听

js 复制代码
class FileProcessor extends EventEmitter {}

const processor = new FileProcessor();

processor.on('read', (data) => console.log('日志记录:', data.length));
processor.on('read', (data) => console.log('缓存更新'));
processor.on('read', (data) => console.log('响应客户端'));

processor.emit('read', bigBuffer);  // 三个监听都执行

例子2:错误约定

js 复制代码
ee.on('error', (err) => console.error('出错了:', err));  // 必须监听!
ee.emit('error', new Error('boom'));  // 不监听会崩溃进程

优势:解耦、灵活、可组合(Stream、http、net底层都用它)。

4.3 Promise/Deferred模式

核心思想

把嵌套回调变成链式,错误统一catch。

生动比喻:回调地狱是"横着爬山",Promise是"顺着绳子往下跳",一步接一步。

Promise 三状态

Pending → Fulfilled / Rejected(不可逆)

当时Deferred模式(第三方库如Q/Bluebird)

js 复制代码
function readFile(filename) {
  const deferred = Q.defer();
  fs.readFile(filename, (err, data) => {
    err ? deferred.reject(err) : deferred.resolve(data);
  });
  return deferred.promise;
}

链式使用

js 复制代码
readFile('a.txt')
  .then(data => {
    console.log('a:', data);
    return readFile('b.txt');  // 返回新Promise,继续链
  })
  .then(data => {
    console.log('b:', data);
    throw new Error('出错');
  })
  .catch(err => console.error('统一捕获:', err));  // 任何一层错误都到这里

例子1:并行Promise.all

js 复制代码
Promise.all([readFile('a'), readFile('b'), readFile('c')])
  .then(([a, b, c]) => console.log('三个文件都读完'))
  .catch(err => console.error('任何一个失败就进来'));

例子2:值穿透

js 复制代码
Promise.resolve(1)
  .then(() => '字符串')          // 返回非Promise,直接传下一个
  .then(str => console.log(str)); // '字符串'

优势:扁平化、错误冒泡、并行控制。

4.4 流程控制库

4.4.1 尾触发与nextTick

同步尾触发会栈溢出,异步用nextTick推到队列尾。

例子

js 复制代码
function tail() {
  process.nextTick(() => tail());  // 不会栈溢出
}

4.4.2 async库(最经典,至今流行)

  • series:串行
  • parallel:并行
  • waterfall:串行且传值
  • auto:复杂依赖

例子1:waterfall串行传值

js 复制代码
async.waterfall([
  cb => fs.readFile('a.txt', cb),
  (dataA, cb) => {
    console.log(dataA);
    fs.readFile('b.txt', cb);
  },
  (dataB, cb) => cb(null, '最终结果')
], (err, result) => console.log(result));

例子2:parallel并行

js 复制代码
async.parallel([
  cb => fs.readFile('a.txt', cb),
  cb => fs.readFile('b.txt', cb)
], (err, [a, b]) => console.log(a, b));

4.4.3 Step库(线性写法)

js 复制代码
Step(
  function() { fs.readFile('a.txt', this); },
  function(err, data) {
    console.log(data);
    fs.readFile('b.txt', this);
  }
);

4.4.4 Wind库(编译器风格,接近async/await)

js 复制代码
var read = eval(Wind.compile("async", function() {
  var a = $await(fs.readFileAsync('a.txt'));
  var b = $await(fs.readFileAsync('b.txt'));
  console.log(a, b);
}));
read().start();

4.5 现代补充:async/await(终极形态)

所有方案的集大成:

js 复制代码
async function main() {
  try {
    const a = await readFile('a.txt');
    const b = await readFile('b.txt');
    console.log(a, b);
  } catch (err) {
    console.error(err);  // 统一捕获
  }
}
main();

生动比喻

  • 回调:横着爬山(地狱)
  • EventEmitter:广播喊人
  • Promise:顺绳子跳(链式)
  • async/await:直接走平路(像同步)

总结与收获

第四章告诉你:异步难写,但有层层递进的解决方案。从事件解耦,到Promise链式,再到流程库控制,最后async/await让代码重回"同步美感"。

相关推荐
无心使然2 小时前
vant实现自定义日期时间选择器(年月日时分秒)
前端·vue.js
鱼鱼块2 小时前
React 组件通信实战:从 props 入门到父子协作闭环
前端·react.js·面试
龙猫不热2 小时前
THREE.js 关于Material基类下的depthTest 和 depthWrite的理解
前端·three.js
前端程序猿之路2 小时前
简易版AI知识助手项目 - 构建个人文档智能问答系统
前端·人工智能·python·ai·语言模型·deepseek·rag agent
失败又激情的man2 小时前
爬虫逆向之阿里系cookie acw_sc__v2 逆向分析
前端·javascript·爬虫
小肖爱笑不爱笑2 小时前
Vue Ajax
前端·javascript·vue.js·web
全栈技术负责人2 小时前
我的大前端世界观 (黄玄 - FEDAY 2023)
前端
changlianzhifu12 小时前
分账系统:从“资金管道“到“增长引擎“,重塑商业价值分配新范式
java·服务器·前端
异界蜉蝣2 小时前
前端模块化的演进史:从混乱到秩序
前端