异步/等待超时

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 方法结合使用。

【讨论】:

以上是关于异步/等待超时的主要内容,如果未能解决你的问题,请参考以下文章

量角器:失败:超时等待异步角度任务在11秒后完成

XCUI 测试因异步等待失败而失败:超过 30 秒的超时,未达到预期

同步与异步

go并发-异步和超时控制

go并发-异步和超时控制

go并发-异步和超时控制