凡君所撰之异步函数,皆假以网络协从,伺应不爽,而数据库亦无颠踬。然于生产之际,此等假设永难恒持。
今有二阶函数,各不过十五行,可令任一异步函数坚韧,而无需触及其内里。
其弊在
尔有异步函数。或调用API,或查询数据库,或越网络读文件。
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
终有二事不谐:
- 时或失之,尔欲重试之
- 时或悬之无期,尔欲限时而弃之
尔可于每函数内嵌重试与超时之理。或一书写之,工致,而裹所欲之函数。
retry — 失败则自动重试
export function retry(count, callback) {
return async function (...args) {
let attempts = 0;
let lastError;
while (attempts <= count) {
try {
return await callback(...args);
} catch (err) {
lastError = err;
attempts++;
}
}
throw lastError;
};
}
其运作之理
retry乃高阶函数,取一函数而返一嵌重试之新函数。原函数不损。
此while (attempts <= count)之设,意有所在。若count为3,则attempts时,循环乃行。0, 1, 2, 3 — 是谓四度戮之:初试一,复试三。此合乎"三度重试"之自然义。
若得,return await callback(...args) 即时而止,不复循环。若失,则谬误存于lastError,而attempts 渐增。及至试竭,乃抛最后之谬,非泛泛之咎。new Error('Max retries reached')者,实为回调所生之谬误也。调用者得义蕴之警示,非徒饰以囊括之。
用法
const resilientFetch = retry(3, fetchUserData);
// Works exactly like fetchUserData, but retries up to 3 times on failure
const user = await resilientFetch('user_123');
为何await于try中要紧
try {
return await callback(...args); // ✓ catches rejected promises
} catch (err) { ... }
无之await,弃之不允之诺,竟全脱试/捕之范围。
try {
return callback(...args); // ✗ returns a pending promise — catch never fires
} catch (err) { ... }
await启封于试块内之诺,故拒可捕。此乃异步/等待之常见谬误也。retry唯其得此正,故能效。
timeout— 逾期则弃
export function timeout(delay, callback) {
return async function (...args) {
const timer = new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), delay)
);
return Promise.race([callback(...args), timer]);
};
}
工作原理
Promise.race以首个解决之承诺为准,或成或败。此函数设一竞速之戏:
-
callback(...args)——实为之务 -
timer——一承诺,于delay毫秒后即败
。若回调及时完成,其值得胜,timer遂成虚设。若delay毫秒先逝。timer拒之Error('timeout')而回调之终局结果,亦所不顾也。
察计时器之承诺,乃所构建也(_, reject)— 永无决,唯拒。此可保计时器绝无偶得胜之机;惟可败而中断。
用之
const limitedFetch = timeout(5000, fetchUserData);
try {
const user = await limitedFetch('user_123');
} catch (e) {
if (e.message === 'timeout') {
console.error('Request took too long');
}
}
合而为一
二函数皆返异步函数,其签名为输入之同——是故可相合无碍.
// Retry up to 3 times, but abandon any single attempt after 5 seconds
const resilientFetch = retry(3, timeout(5000, fetchUserData));
await resilientFetch('user_123');
每试之状如下:
-
timeout(5000, fetchUserData)以五秒之计时竞速于取物 - 若其超时,
timeout拒绝,Error('timeout') -
retry捕捉此拒绝,增尝试之数,复试之。 - 三试皆败,
retry复抛末次之误。
四试,每试限五秒,总计至多二十秒。皆出二可组合之函数,一语而设。
何故值存此?
彼等不更其本函数。 fetchUserData 毫无更改。汝可随处用之,无论重试/超时与否。
彼等透明转发其引数。 ...args 皆通之,被裹之函数于调用者视之,无异于本函数。
彼等存其错。 retry 再抛之。lastError,非新异之误。timeout,拒以名目之Error('timeout'),可察于文。呼者恒知其故实。
,彼辈相成。,盖二皆返异步之函数,其签貌相合,故可层叠无序,相协无碍,互不知晓。
此法
二法同构:
higherOrderFn(config, callback) {
return async function (...args) {
// enhanced behaviour around callback(...args)
}
}
此乃异步函数之裱花术。汝但书裱花一次,即可施诸需之异步函数——无承袭,无类,无改本。但见函数包函数耳。
此乃微纹也。一旦凝神细察,处处可见。












