《深入浅出 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让代码重回"同步美感"。

相关推荐
随风一样自由7 小时前
【前端独角兽】刚进入前端领域的前端开发用jQuery还是Vue3?
前端·javascript·vue3·jquery
IMPYLH8 小时前
HTML 的 <data> 元素
前端·html
YHHLAI8 小时前
[特殊字符] 面试中的 Promise —— 从入门到精通
前端·javascript·面试
kyriewen10 小时前
我扒了 GPT-5.6 的全部编程跑分——有一项 OpenAI 没敢放出来
前端·gpt·ai编程
前端H11 小时前
微前端 v3 架构升级:从加载隔离到状态协同
前端·架构·状态模式
陆枫Larry12 小时前
小程序包体积优化:用 SVG 图片替换 iconfont,并保留 CSS 控色能力
前端
环境栈笔记13 小时前
多账号浏览器选型检查清单:Profile、权限、Session 和任务日志怎么评估
前端·人工智能·后端·自动化
前端炒粉13 小时前
手写promise
java·前端·javascript
LaughingZhu13 小时前
智能体经典范式构建:ReAct、Plan-and-Solve 与 Reflection
前端·react.js·前端框架
jsonbro13 小时前
手把手带你实现发布订阅模式
前端·vue.js·设计模式