JavaScript 中的结构化并发:超越 Promise.all
I’m happy to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source line and all formatting exactly as you requested.
非结构化异步代码的问题
JavaScript 异步代码存在作用域问题。你触发 Promise 并期望它们能够干净地完成——或失败。当在执行过程中出现问题时,清理工作就由你来负责。
部分失败示例
async function loadDashboard(userId: string) {
const [user, orders, analytics] = await Promise.all([
getUser(userId),
getOrders(userId), // 这将在 2 秒后抛出异常
getAnalytics(userId), // 仍在运行中!
]);
// getAnalytics 永远不会被取消
}当 getOrders 被拒绝时,Promise.all 会被拒绝——但 getAnalytics 仍在后台运行,消耗资源并可能写入过时的数据。
单独处理每个结果
async function loadDashboard(userId: string) {
const results = await Promise.allSettled([
getUser(userId),
getOrders(userId),
getAnalytics(userId),
]);
const [userResult, ordersResult, analyticsResult] = results;
const user = userResult.status === 'fulfilled' ? userResult.value : null;
const orders = ordersResult.status === 'fulfilled' ? ordersResult.value : [];
if (analyticsResult.status === 'rejected') {
console.error('Analytics failed:', analyticsResult.reason);
}
return {
user,
orders,
analytics:
analyticsResult.status === 'fulfilled' ? analyticsResult.value : null,
};
}超时与取消
可中止的 fetch 与超时
async function fetchWithTimeout(url: string, timeoutMs: number): Promise {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
return response.json();
} finally {
clearTimeout(timeoutId);
}
}React 清理示例
function useUserData(userId: string) {
const [data, setData] = useState(null);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/users/${userId}`, { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') throw err;
// AbortError 是在清理时预期的,忽略它
});
return () => controller.abort(); // 清理
}, [userId]);
return data;
}通用超时包装器
function withTimeout(promise: Promise, ms: number): Promise {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}并发模式
最快的缓存或网络
async function getWithFallback(key: string) {
return Promise.race([
redis.get(key).then(v => JSON.parse(v!)), // cache
db.slowQuery(key), // database
]);
}首个成功的 CDN 镜像
async function fetchFromCDN(path: string) {
return Promise.any([
fetch(`https://cdn1.example.com${path}`),
fetch(`https://cdn2.example.com${path}`),
fetch(`https://cdn3.example.com${path}`),
]);
// Rejects only if ALL fail (AggregateError)
}受控并行的批处理
一次并行运行 1 000 个任务可能会压垮数据库和 API。将它们分批处理:
async function processInBatches(
items: T[],
processor: (item: T) => Promise,
concurrency: number
): Promise {
const results: R[] = [];
for (let i = 0; i limit(() => processUser(user)))
);// 所有 1 000 个任务已排队,但一次只运行 10 个使用异步生成器进行流式处理
当您需要在不将所有数据加载到内存中的情况下处理大型数据集时:
选择合适的并发原语
| Primitive | When to Use |
|---|---|
Promise.all | 所有任务必须成功;一旦出现错误即快速失败 |
Promise.allSettled | 需要获取每个任务的结果,无论成功还是失败 |
Promise.race | 第一个完成的任务(成功或失败)决定结果 |
Promise.any | 第一个成功的任务;仅在全部失败时才会被拒绝 |
AbortController | 取消进行中的请求(例如 fetch) |
p-limit / semaphore | 受控的并行度以避免过载 |
| Async generators | 在不完整生成全部数据的情况下流式处理大型序列 |