6 种防止生产环境中部分失败的异步 JavaScript 模式
Source: Dev.to
大多数异步代码在工作流进行到一半时都能正常运行,但一旦某一步失败,就会导致重复收费、数据缺失或静默损坏。
这些模式可防止生产环境中的部分失败。
1. 用补偿步骤替代顺序 Await
顺序代码看起来简洁,但在部分失败时会出现问题。
Before
async function processOrder(orderId: string) {
const order = await fetchOrder(orderId)
const payment = await chargeCustomer(order.customerId, order.total)
const shipment = await createShipment(order.items, order.address)
return { order, payment, shipment }
}如果发货失败,付款已经完成——没有回滚。
After
async function processOrder(orderId: string) {
const order = await fetchOrder(orderId)
let payment
try {
payment = await chargeCustomer(order.customerId, order.total)
} catch {
throw new Error('PAYMENT_FAILED')
}
try {
const shipment = await createShipment(order.items, order.address)
return { order, payment, shipment }
} catch {
await refundPayment(payment.id).catch(() => {
logger.fatal('REFUND FAILED', { orderId })
})
throw new Error('SHIPMENT_FAILED')
}
}你显式地定义回滚逻辑——这正是实际系统的做法。
2. 提前启动独立的 Promise
大多数开发者不小心把独立的工作串行化了。
之前
async function loadData(userId: string) {
const user = await fetchUser(userId)
const analytics = await fetchAnalytics(userId)
return { user, analytics }
}总耗时 = 两个调用的时间之和。
之后
async function loadData(userId: string) {
const analyticsPromise = fetchAnalytics(userId)
const user = await fetchUser(userId)
const analytics = await analyticsPromise
return { user, analytics }
}在不改变逻辑的前提下,你可以减少延迟。这个模式经常出现在高级面试中。
3. 使用 Promise.allSettled 保护多调用流程
仪表盘不应因某个 API 宕机而整体失败。
之前
const [users, orders, stats] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchStats()
])一次失败会导致所有请求都失败。
之后
const results = await Promise.allSettled([
fetchUsers(),
fetchOrders(),
fetchStats()
])
const [users, orders, stats] = results.map(r =>
r.status === 'fulfilled' ? r.value : null
)现在可以渲染部分数据。失败变为可观察的,而不是灾难性的。
4. 仅对瞬态错误进行重试并使用退避
重试所有操作比不重试更糟。
之前
await fetch('/api/payment')一次网络波动就会中断流程。
之后
async function retry(fn, attempts = 3) {
for (let i = 0; i setTimeout(r, 2 ** i * 100))
}
}
throw new Error('FAILED_AFTER_RETRIES')
}
await retry(() => fetch('/api/payment'))仅在有意义时才进行重试,避免对 API 进行猛烈请求。这在处理如Node.js memory leak debugging scenarios中描述的不可靠外部系统时尤为关键,因为错误的重试会放大系统压力。
5. 使用 AbortController 取消过时请求
竞争条件是最常见的生产环境 bug 之一。
Before
useEffect(() => {
fetch(`/api/search?q=${query}`)
.then(r => r.json())
.then(setResults)
}, [query])旧的响应会覆盖新的响应。
After
useEffect(() => {
const controller = new AbortController()
fetch(`/api/search?q=${query}`, {
signal: controller.signal
})
.then(r => r.json())
.then(setResults)
.catch(err => {
if (err.name !== 'AbortError') throw err
})
return () => controller.abort()
}, [query])现在只有最新的请求会生效,不会出现过时的 UI。
6. 限制并发而不是淹没 API
一次性发送 100 个请求会导致被限流或封禁。
Before
await Promise.all(ids.map(id => fetchItem(id)))无限并发。
After
async function limit(tasks, concurrency) {
const results = []
let i = 0
async function worker() {
while (i () => fetchItem(id))
await limit(tasks, 5)你可以控制吞吐量,使系统在负载下保持稳定。
结束语
今天就挑选一个现有的异步流程,添加补偿或取消机制。这单独就能消除一整类生产环境的错误。
如果你的代码只假设成功路径,那么它已经出现问题了。