Promise.withResolvers 手动控制 Promise

在 ECMAScript 2024 (ES15) 中,JavaScript 引入了一个新的 Promise 工具方法 Promise.withResolvers(),它为开发者提供了更灵活的方式来手动控制 Promise 的解析和拒绝。这个新特性解决了传统 Promise 构造函数模式中的一些痛点,特别是在需要将 Promise 的解析控制权传递给外部代码的情况下。

传统 Promise 构造的问题

在现有的 JavaScript 中,我们通常这样创建一个 Promise:

javascript 复制代码
const promise = new Promise((resolve, reject) => {
  // 异步操作
  someAsyncOperation((result) => {
    if (result.success) {
      resolve(result.data);
    } else {
      reject(result.error);
    }
  });
});

这种方式虽然有效,但在某些场景下存在局限性:

  1. resolve/reject 只能在构造函数内部访问:无法将解析函数传递给外部代码
  2. 代码组织不够灵活:特别是当需要在多个地方控制 Promise 的解析时

Promise.withResolvers 的解决方案

Promise.withResolvers() 提供了一种更优雅的方式:

javascript 复制代码
const { promise, resolve, reject } = Promise.withResolvers();

这个方法返回一个对象,包含三个属性:

  • promise:新创建的 Promise 对象
  • resolve:用于解析该 Promise 的函数
  • reject:用于拒绝该 Promise 的函数

使用场景

1. 事件监听器

javascript 复制代码
function waitForClick(element) {
  const { promise, resolve } = Promise.withResolvers();
  
  element.addEventListener('click', function handler(event) {
    element.removeEventListener('click', handler);
    resolve(event);
  }, { once: true });
  
  return promise;
}

2. 流式处理

javascript 复制代码
async function processStream(stream) {
  const { promise, resolve, reject } = Promise.withResolvers();
  
  stream.on('data', (data) => {
    // 处理数据
  });
  
  stream.on('end', () => resolve());
  stream.on('error', (err) => reject(err));
  
  return promise;
}

3. 跨作用域控制

javascript 复制代码
let globalResolve;

async function startOperation() {
  const { promise, resolve } = Promise.withResolvers();
  globalResolve = resolve;
  
  await promise;
  console.log('操作完成');
}

// 其他地方
function completeOperation() {
  globalResolve();
}

与传统模式的对比

特性 传统 Promise 构造函数 Promise.withResolvers
解析函数作用域 仅限于构造函数内部 可传递到任何地方
代码组织 需要嵌套 更扁平化
多个控制点 困难 容易
可读性 一般 更好

浏览器兼容性

截至 2024 年,Promise.withResolvers() 已在所有现代浏览器中实现:

  • Chrome 119+
  • Firefox 121+
  • Safari 17+
  • Edge 119+
  • Node.js 21+

对于旧环境,可以使用以下 polyfill:

javascript 复制代码
if (!Promise.withResolvers) {
  Promise.withResolvers = function() {
    let resolve, reject;
    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });
    return { promise, resolve, reject };
  };
}

最佳实践

  1. 避免滥用:只在真正需要将解析控制权外露时使用
  2. 清理引用:不再需要解析函数时,将其设为 null 防止内存泄漏
  3. 错误处理:仍然要确保适当的错误处理
  4. 文档注释:当解析函数被传递到外部时,添加清晰的文档说明

结论

Promise.withResolvers() 是 ECMAScript 2024 中一个简单但强大的新增功能,它为 Promise 的控制提供了更大的灵活性。通过将 Promise 的解析函数从构造函数中解耦出来,它解决了许多实际开发中的痛点,特别是在事件处理、流控制和跨作用域协调等场景下。虽然它不会取代传统的 Promise 构造函数,但确实为 JavaScript 开发者的工具箱增添了一个有价值的工具。