我有一个简单的timeout
函数,该函数包装了一个带有超时的异步函数,以确保它在经过预设的时间后失败。超时功能如下:
export default async function<T = any>(
fn: Promise<T>,
ms: number,
identifier: string = ""
): Promise<T> {
let completed = false;
const timer = setTimeout(() => {
if (completed === false) {
const e = new Error(`Timed out after ${ms}ms [ ${identifier} ]`);
e.name = "TimeoutError";
throw e;
}
}, ms);
const results = await fn;
completed = true;
timer.unref();
return results;
}
然后我在以下简单代码段中使用此函数,以确保将 fetch 请求(使用node-fetch
实现)转换为文本输出:
let htmlContent: string;
try {
htmlContent = await timeout<string>(
response.text(),
3000,
`waiting for text conversion of network payload`
);
} catch (e) {
console.log(
chalk.grey(`- Network response couldn\'t be converted to text: ${e.message}`)
);
problemsCaching.push(business);
return business;
}
在多次迭代中运行此代码时,大多数URL端点都提供了可轻松转换为文本的有效负载,但偶尔会出现错误,似乎只是挂起了 fetch 调用。在这种情况下,实际上会触发超时,但是抛出的TimeoutError
不会被catch块捕获,而是终止正在运行的程序。
我有点困惑。我现在确实经常使用async / await,但据我了解,我可能仍会遇到一些困难。谁能解释我如何有效地捕获并处理此错误?
答案 0 :(得分:4)
仅当其直接封闭函数具有某种错误处理功能时,才会捕获引发的错误。传递给setTimeout
的匿名函数本身不是async
函数,因此如果 separate async
抛出,timeout
函数不会停止执行一段时间后:
const makeProm = () => new Promise(res => setTimeout(res, 200));
(async () => {
setTimeout(() => {
throw new Error();
}, 200);
await makeProm();
console.log('done');
})()
.catch((e) => {
console.log('caught');
});
这似乎是使用Promise.race
的好时机:将fetch
Promise
传递给它,同时将Promise
传递给它,该ms
在传递{{1}之后会被拒绝}参数:
async function timeout(prom, ms) {
return Promise.race([
prom,
new Promise((res, rej) => setTimeout(() => rej('timeout!'), ms))
])
}
(async () => {
try {
await timeout(
new Promise(res => setTimeout(res, 2000)),
500
)
} catch(e) {
console.log('err ' + e);
}
})();
答案 1 :(得分:1)
此错误在单独的调用堆栈中发生,因为它是从回调内抛出的。它与try
/ catch
块中的同步执行流完全分开。
您要在超时或成功回调中操作相同的Promise对象。这样的事情应该会更好:
return new Promise( ( resolve, reject ) => {
let rejected = false;
const timer = setTimeout( () => {
rejected = true;
reject( new Error( 'Timed out' ) );
}, ms ).unref();
fn.then( result => {
clearTimeout( timer );
if ( ! rejected ) {
resolve( result ) );
}
} );
} );
在没有rejected
和clearTimeout
的情况下也可以正常工作,但是通过这种方式,您可以确保同时调用resolve
或reject
,而不是同时调用。
您会发现我在这里的任何地方都没有使用await
或throw
!如果您在使用异步代码时遇到麻烦,最好先使用单一样式(使用await
使用所有回调或所有Promise,或使用所有“同步”样式)来编写它。
尤其是,不能仅使用await
编写此示例,因为您需要同时运行两个任务(超时和请求)。您可能使用Promise.race()
,但仍需要Promise
才能使用。
答案 2 :(得分:-1)
我将提供一些一般规则,因为已经给出了答案。
默认情况下,尝试/捕获是同步的。这意味着如果 异步函数在同步try / catch中引发错误 块,没有错误抛出。