Featured image of post 高级 JavaScript Promise 模式:掌握异步控制流Featured image of post 高级 JavaScript Promise 模式:掌握异步控制流

高级 JavaScript Promise 模式:掌握异步控制流

深入研究高级 JavaScript Promise 模式,包括并发限制、错误处理策略和生产应用程序的异步控制流。

Promise 是现代 JavaScript 的基础,但掌握异步控制流需要超越基本的 .then() 链。本文探讨了中高级开发人员生产应用程序所需的高级 Promise 模式,从并发控制到错误处理和取消。

超越基础的承诺景观

JavaScript 在 Promise 上提供了四种静态方法来组合异步操作,每种方法适合不同的场景:

方法解决时间拒绝何时使用案例
占位符_0所有承诺兑现任何承诺都被拒绝并行 API 调用,全部必需
占位符_0所有的承诺都兑现了从来没有结果好坏参半,记录所有结果
占位符_0第一个承诺落定​​第一个承诺被拒绝超时竞争条件
占位符_0第一个承诺解决了所有承诺均被拒绝首次成功响应模式
const results = await Promise.allSettled([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json()),
  fetch("/api/comments").then(r => r.json()),
]);
const successful = results.filter(r => r.status === "fulfilled").map(r => r.value);

当您需要处理部分结果(即使某些操作失败)时,Promise.allSettled 特别有用,例如批量数据同步,其中单个记录失败不应中止整个批次。


并发控制

同时运行太多 Promise 可能会压垮 API、数据库或浏览器。并发限制器对任务进行排队并确保只有指定数量的任务同时执行:

async function pMap(items, fn, concurrency = 5) {
  const results = [];
  const executing = new Set();
  for (const item of items) {
    const p = fn(item).then(result => {
      executing.delete(p);
      return result;
    });
    executing.add(p);
    results.push(p);
    if (executing.size >= concurrency) {
      await Promise.race(executing);
    }
  }
  return Promise.all(results);
}

// Process 100 URLs with max 5 concurrent requests
const data = await pMap(urls, url => fetch(url).then(r => r.json()), 5);

此模式支持背压:当达到并发限制时,循环将暂停,直到至少一个任务完成。对于更复杂的需求,p-limitp-queuep-throttle 等库提供了附加功能,包括优先级队列和速率限制。


异步/等待最佳实践

循环中的顺序 await 是一个常见的性能缺陷。每次迭代都会等待前一个 Promise 解析后再开始下一个 Promise,即使没有依赖项也是如此:

// Slow: sequential execution
for (const id of ids) {
  const data = await fetch(`/api/items/${id}`).then(r => r.json());
  process(data);
}

// Fast: parallel execution
const results = await Promise.all(
  ids.map(id => fetch(`/api/items/${id}`).then(r => r.json()))
);
results.forEach(process);

仅当每个步骤依赖于前一个结果时才使用顺序 await 。对于独立操作,始终首选 Promise.all 以最大化吞吐量。异步函数中的错误处理受益于带有类型化错误类的结构化 try-catch:

class ApiError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

async function fetchData(url) {
  const response = await fetch(url);
  if (!response.ok) {
    throw new ApiError(response.status, await response.text());
  }
  return response.json();
}

错误处理和弹性

生产应用程序必须优雅地处理 Promise 拒绝。全局拒绝跟踪捕获未处理的错误:

// Browser
window.addEventListener("unhandledrejection", (event) => {
  console.error("Unhandled rejection:", event.reason);
  event.preventDefault();
});

// Node.js
process.on("unhandledRejection", (reason, promise) => {
  console.error("Unhandled rejection at:", promise, "reason:", reason);
});

对于不稳定的外部服务,断路器模式可以防止重复调用失败的端点:

class CircuitBreaker {
  constructor(fn, options = {}) {
    this.fn = fn;
    this.failureCount = 0;
    this.threshold = options.threshold || 5;
    this.timeout = options.timeout || 30000;
    this.state = "CLOSED";
  }
  async call(...args) {
    if (this.state === "OPEN") {
      throw new Error("Circuit breaker is open");
    }
    try {
      const result = await this.fn(...args);
      this.failureCount = 0;
      return result;
    } catch (error) {
      this.failureCount++;
      if (this.failureCount >= this.threshold) {
        this.state = "OPEN";
        setTimeout(() => { this.state = "HALF_OPEN"; }, this.timeout);
      }
      throw error;
    }
  }
}

使用 AbortController 取消

Promise 本身并不是可取消的,但 AbortController 提供了一种标准机制来中止获取请求并通过异步流传播取消:

function createTimeoutSignal(ms) {
  const controller = new AbortController();
  setTimeout(() => controller.abort(), ms);
  return controller.signal;
}

async function fetchWithTimeout(url, ms = 5000) {
  const signal = createTimeoutSignal(ms);
  try {
    const response = await fetch(url, { signal });
    return response.json();
  } catch (error) {
    if (error.name === "AbortError") {
      throw new Error(`Request timed out after ${ms}ms`);
    }
    throw error;
  }
}

此模式支持任何异步操作超时、请求重复数据删除(其中正在进行的请求在调用方之间共享)以及组件卸载时清理异步流。


结论

高级 Promise 模式将异步 JavaScript 从容易出错的状态转变为健壮且可维护的状态。为每个场景选择正确的 Promise 组合器,限制并发以保护下游服务,构建异步函数以提高可读性,实现断路器以实现弹性,并使用 AbortController 来取消流。这些模式构成了现代 JavaScript 应用程序中生产级异步代码的基础。