← 中文资源

简体中文版本

结构化并发

结构化并发把子任务绑到父级的词法作用域:取消、错误与完成沿任务树传播,不再泄漏孤儿任务。

阅读英文原文

概念定义

代码块约束资源生命周期,而传统并发不约束任务。游离的 promise 是无主资源:没人取消它、没人看它的错误、没人限定它的寿命。结构化并发给任务与局部变量同样的生命周期规则。

工作机制

作用域开启、子任务注册、汇合点强制完成语义:等全部、首个成功即取消其余、或聚合结果。失败的子任务把作用域标记为失败并请求取消兄弟;汇合点重抛代表性错误。作用域退出保证没有任务活得比代码块更久。

使用场景

向多个服务扇出的请求处理器、带早退条件的批任务、以及“部分失败就该全停”的场景。要与取消令牌或协作让点配合,子任务才会真正响应作用域取消。

常见误区

“等价于 Promise.all”低估了它:all 只等结果,既无子任务取消也无未决拒绝的兜底。在忽略取消的子任务里写阻塞,会把结构化纪律变成挂死。自管生命周期的遗留 API(定时器、watcher)需要适配器注册进作用域;真正的后台工作确需逃逸舱,但必须显式、可见、可评审。

代码示例

interface RetryableError extends Error {
  retryable?: boolean;
}

class PermanentError extends Error {
  retryable = false;
}

function classify(err: unknown): RetryableError {
  if (err instanceof PermanentError) return err;

  const message = err instanceof Error ? err.message : String(err);
  const transient =
    /timeout|econnreset|socket hang up|503|429/i.test(message);

  return Object.assign(
    err instanceof Error ? err : new Error(message),
    { retryable: transient },
  );
}

async function withRetry<T>(
  fn: () => Promise<T>,
  { attempts = 3, baseMs = 200 } = {},
): Promise<T> {
  let lastError: RetryableError;

  for (let i = 1; i <= attempts; i++) {
    try {
      return await fn();
    } catch (raw) {
      lastError = classify(raw);

      if (!lastError.retryable || i === attempts) {
        throw lastError;
      }
      await new Promise((r) => setTimeout(r, baseMs * 2 ** (i - 1)));
    }
  }
  throw lastError!;
}

const result = await withRetry(async () => {
  if (Math.random() < 0.7) {
    const err = new Error('upstream 503 unavailable');
    throw err;
  }
  return 'settled';
});
console.log('outcome:', result);
async function asyncPool<T, R>(
  concurrency: number,
  items: readonly T[],
  mapper: (item: T, index: number) => Promise<R>,
): Promise<R[]> {
  const results = new Array<R>(items.length);
  let cursor = 0;

  const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
    while (cursor < items.length) {
      const index = cursor;
      const item = items[index];
      cursor += 1;
      results[index] = await mapper(item, index);
    }
  });

  await Promise.all(workers);
  return results;
}

const CONCURRENCY = 4;

const enriched = await asyncPool(CONCURRENCY, ['a', 'b', 'c', 'd', 'e', 'f'], async (id, i) => {
  await new Promise((r) => setTimeout(r, 20));
  return { id, position: i };
});

console.log('order preserved:', enriched.map((e) => e.id).join(''));

const withFailure = await asyncPool(2, ['x', 'y', 'z'], async (id) => {
  if (id === 'y') throw new Error('boom on ' + id);
  return id.toUpperCase();
}).catch((err) => err.message);

console.log('failure propagated:', withFailure);