4.3 Promise/Deferred模式

19 阅读2分钟

这一小节是第四章的重点之一,朴灵作者在2013年时敏锐地捕捉到Promise即将成为异步编程的未来趋势(当时原生Promise还没普及,主要靠第三方库如Bluebird、Q、when.js)。他详细讲解了Promise的核心概念、Deferred模式,以及它如何优雅地解决回调地狱。

为什么需要Promise?

  • 事件发布/订阅适合“一对多”解耦,但串行异步操作仍容易嵌套(回调地狱)。
  • Promise的目标:把嵌套变成链式,让异步代码更线性、可读。

Promise的核心概念(A+规范)

Promise代表一个异步操作的最终结果,有三种状态:

  • Pending:进行中
  • Fulfilled(Resolved):成功
  • Rejected:失败

状态不可逆,一旦确定就永久不变。

核心方法:

  • then(onFulfilled, onRejected):链式调用,返回新Promise
  • catch(onRejected):捕获错误
  • finally(后来添加)

Deferred模式

当时很多库用Deferred对象来创建Promise(现在原生用new Promise):

  • Deferred有resolve/reject方法控制状态。
  • Promise是只读的“承诺”。

示例(书里风格,用当时流行库模拟):

// 模拟一个异步读取文件返回Promise的函数
function readFile(filename) {
  const deferred = Q.defer();  // Q库的Deferred

  fs.readFile(filename, 'utf8', (err, data) => {
    if (err) deferred.reject(err);  // 失败
    else deferred.resolve(data);    // 成功
  });

  return deferred.promise;  // 返回只读Promise
}

// 使用:链式调用
readFile('a.txt')
  .then((data) => {
    console.log('a.txt:', data);
    return readFile('b.txt');  // 返回新Promise,继续链
  })
  .then((data) => {
    console.log('b.txt:', data);
    return readFile('c.txt');
  })
  .then((data) => console.log('c.txt:', data))
  .catch((err) => console.error('出错:', err));  // 统一捕获

对比回调地狱,代码从“金字塔”变成了“直线链”!

Promise的优势

  1. 避免嵌套:每个then返回新Promise,可无限链式。
  2. 错误冒泡:一个catch捕获整条链的错误。
  3. 值穿透:如果then返回非Promise值,自动传递给下一个。
  4. 并行控制:Promise.all / Promise.race

示例:并行读取多个文件

Promise.all([readFile('a.txt'), readFile('b.txt'), readFile('c.txt')])
  .then(([a, b, c]) => console.log('全部完成', a, b, c))
  .catch(err => console.error(err));

书中的注意点

  • Promise是“未来值”的占位符。
  • then可以同步或异步执行(取决于前一个Promise状态)。
  • 避免“Promise地狱”(滥用导致复杂链),但比回调好多了。

现代视角补充(2025年)

现在我们直接用原生Promise + async/await:

async function readFiles() {
  try {
    const a = await readFile('a.txt');
    const b = await readFile('b.txt');
    const c = await readFile('c.txt');
    console.log(a, b, c);
  } catch (err) {
    console.error(err);
  }
}