try/catch 块未捕获 async/await 错误
try/catch block not catching async/await error
我有一个简单的 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,但在我的理解中可能仍有一些粗糙的边缘。谁能解释一下我如何有效地捕获并处理这个错误?
抛出的错误只有在 直接包含的函数 有某种错误处理时才会被捕获。传递给 setTimeout
的匿名函数不是 async
函数本身,因此如果 separate [=15],async
函数不会停止执行=] 一段时间后抛出:
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
在传递 [=] 之后拒绝20=]参数:
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);
}
})();
此错误发生在单独的调用堆栈中,因为它是从回调中抛出的。它与 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
!如果您在使用异步代码时遇到问题,最好先使用单一样式编写它(所有回调,或所有承诺,或所有 "synchronous" 样式都使用 await
)。
这个例子特别不能只用await
来写,因为你需要同时有两个任务运行(超时和请求)。您可能会使用 Promise.race()
,但您仍然需要 Promise
才能使用。
我会提供一些一般规则,因为答案已经给出。
Try/catch is by default synchronous. That means that if an
asynchronous function throws an error in a synchronous try/catch
block, no error throws.
我有一个简单的 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,但在我的理解中可能仍有一些粗糙的边缘。谁能解释一下我如何有效地捕获并处理这个错误?
抛出的错误只有在 直接包含的函数 有某种错误处理时才会被捕获。传递给 setTimeout
的匿名函数不是 async
函数本身,因此如果 separate [=15],async
函数不会停止执行=] 一段时间后抛出:
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
在传递 [=] 之后拒绝20=]参数:
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);
}
})();
此错误发生在单独的调用堆栈中,因为它是从回调中抛出的。它与 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
!如果您在使用异步代码时遇到问题,最好先使用单一样式编写它(所有回调,或所有承诺,或所有 "synchronous" 样式都使用 await
)。
这个例子特别不能只用await
来写,因为你需要同时有两个任务运行(超时和请求)。您可能会使用 Promise.race()
,但您仍然需要 Promise
才能使用。
我会提供一些一般规则,因为答案已经给出。
Try/catch is by default synchronous. That means that if an asynchronous function throws an error in a synchronous try/catch block, no error throws.