异步/等待超时
Posted
技术标签:
【中文标题】异步/等待超时【英文标题】:Timeout in async/await 【发布时间】:2016-09-04 08:27:12 【问题描述】:我使用的是 Node.js 和 TypeScript,我使用的是 async/await
。
这是我的测试用例:
async function doSomethingInSeries()
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
我想为整个函数设置一个超时时间。 IE。如果res1
需要 2 秒,res2
需要 0.5 秒,res3
需要 5 秒,我希望有一个超时,在 3 秒后让我抛出一个错误。
正常的setTimeout
调用是个问题,因为范围丢失了:
async function doSomethingInSeries()
const timerId = setTimeout(function()
throw new Error('timeout');
);
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
clearTimeout(timerId);
return 'simle';
而且我无法用普通的Promise.catch
捕捉它:
doSomethingInSeries().catch(function(err)
// errors in res1, res2, res3 will be catched here
// but the setTimeout thing is not!!
);
关于如何解决的任何想法?
【问题讨论】:
你在使用特定的 promise 库吗? 不,只是标准的承诺。 所以 2 + 0.5 + 5 + 3 超时 11.5 秒? 【参考方案1】:您可以使用Promise.race
进行超时:
Promise.race([
doSomethingInSeries(),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 11.5e3))
]).catch(function(err)
// errors in res1, res2, res3 and the timeout will be caught here
)
如果不将 setTimeout
包装在 Promise 中,您将无法使用它。
【讨论】:
@nkint:我们不需要清除它,因为当doSomethingInSeries()
首先解决时不会考虑拒绝。我们可以清除它,但这会相当复杂(race
最好有办法取消较慢的 Promise,但这对于原生 Promise 是不可能的)。
也许我在测试异步的东西时遇到了问题,但在我看来,即使race
拒绝,doSomethingInSeries 也会继续自己的东西。如果超时拒绝,如何确保停止doSomethingInSeries
内的所有内容?
@nkint:你不能停止一个承诺——它只代表一个结果(免责声明:除非你使用支持取消的承诺实现)。因此,如果您需要对正在进行的事情做任何事情,您需要手动完成(就像您在下面的答案中使用 clearTimeout
所做的那样)。
it only represents a result
- 变化清晰,谢谢。有点乱。一些承诺我在doSomethingInSeries
中的await
-ing 是对request
的重试呼叫。我需要其他方法来处理它,无论如何,谢谢。
要拥有 setTimeout
/ clearTimeout
的 promise-wrapped 版本,您可以查看 await-timeout【参考方案2】:
好的,我是这样找到的:
async function _doSomethingInSeries()
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
async function doSomethingInSeries(): Promise<any>
let timeoutId;
const delay = new Promise(function(resolve, reject)
timeoutId = setTimeout(function()
reject(new Error('timeout'));
, 1000);
);
// overall timeout
return Promise.race([delay, _doSomethingInSeries()])
.then( (res) =>
clearTimeout(timeoutId);
return res;
);
有人出错了吗?
让我觉得有点奇怪的是,使用 Promises 作为异步策略会导致我们分配太多其他策略需要的对象,但这是题外话。
【讨论】:
如果_doSomethingInSeries()
失败,您不会清除超时。你应该使用try return await Promise.race(…); finally clearTimeout(timeoutId);
【参考方案3】:
@Bergi 的问题是即使您已经拒绝了承诺,doSomethingInSeries
也会继续执行。最好通过超时取消。
这里支持取消:
async function doSomethingInSeries(cancellationToken)
cancellationToken.throwIfCancelled();
const res1 = await callApi();
cancellationToken.throwIfCancelled();
const res2 = await persistInDB(res1);
cancellationToken.throwIfCancelled();
const res3 = await doHeavyComputation(res1);
cancellationToken.throwIfCancelled();
return 'simle';
但最好将取消令牌传递给每个异步函数并在那里使用它。
这里是取消的实现:
let cancellationToken =
cancelled: false,
cancel: function()
this.cancelled = true;
,
throwIfCancelled: function()
if (this.cancelled) throw new Error('Cancelled');
如果你愿意,你可以把它包装成类。
最后的用法:
doSomethingInSeries(cancellationToken);
setTimeout(cancellationToken.cancel, 5000);
请记住,任务不会立即取消,因此不会在 5 秒后准确调用延续(等待、然后或捕获)。保证您可以将此方法与@Bergi 方法结合使用。
【讨论】:
以上是关于异步/等待超时的主要内容,如果未能解决你的问题,请参考以下文章