node.js 异步/等待超时

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37120240/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 20:11:17  来源:igfitidea点击:

Timeout in async/await

node.jstypescripttimeoutpromiseasync-await

提问by nkint

I'm with Node.js and TypeScript and I'm using async/await. This is my test case:

我使用 Node.js 和 TypeScript,我正在使用async/await. 这是我的测试用例:

async function doSomethingInSeries() {
    const res1 = await callApi();
    const res2 = await persistInDB(res1);
    const res3 = await doHeavyComputation(res1);
    return 'simle';
}

I'd like to set a timeout for the overall function. I.e. if res1takes 2 seconds, res2takes 0.5 seconds, res3takes 5 seconds I'd like to have a timeout that after 3 seconds let me throw an error.

我想为整个功能设置一个超时。即如果res1需要 2 秒,res2需要 0.5 秒,res3需要 5 秒我想有一个超时,在 3 秒后让我抛出一个错误。

With a normal setTimeoutcall is a problem because the scope is lost:

正常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';
}

And I cannot catch it with normal Promise.catch:

我无法用 normal 捕捉它Promise.catch

doSomethingInSeries().catch(function(err) {
    // errors in res1, res2, res3 will be catched here
    // but the setTimeout thing is not!!
});

Any ideas on how to resolve?

关于如何解决的任何想法?

回答by Bergi

You can use Promise.raceto make a timeout:

您可以使用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
})

You cannot use setTimeoutwithout wrapping it in a promise.

如果setTimeout不将其包装在 Promise 中,则无法使用。

回答by nkint

Ok I found this way:

好的,我找到了这种方式:

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;
    });

}

Anyone errors?

任何人的错误?

The things that smells a bit to me is that using Promises as asynchronicity strategy will send us to allocate too many object that some other strategy needs but this is off-topic.

对我来说有点味道的事情是,使用 Promises 作为异步策略会让我们分配太多其他策略需要的对象,但这是题外话。

回答by Alexander Danilov

Problem with @Bergi answer that doSomethingInSeriescontinues executing even if you already rejected the promise. It is much better to just cancel by timeout.

doSomethingInSeries即使您已经拒绝了承诺,@Bergi 答案仍会继续执行的问题。最好通过超时取消。

Here is support of cancellation:

这是取消的支持:

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';
}

But it is even better to pass cancellation token to each async function and use it there.

但是最好将取消令牌传递给每个异步函数并在那里使用它。

Here is implementation of cancellation:

这是取消的实现:

let cancellationToken = {
  cancelled: false,
  cancel: function() {
    this.cancelled = true;
  },
  throwIfCancelled: function() {
    if (this.cancelled) throw new Error('Cancelled');
  }
}

You can wrap it as class if you want.

如果需要,您可以将其包装为类。

And finally usage:

最后用法:

doSomethingInSeries(cancellationToken);

setTimeout(cancellationToken.cancel, 5000);

Keep in mind that task is not cancelled immediately, so continuation (awaiting, then or catch) is not called exactly after 5 secs. To guarantee that you can combine this and @Bergi approach.

请记住,任务不会立即取消,因此不会在 5 秒后恰好调用继续(等待、然后或捕获)。为了保证您可以将此方法与@Bergi 方法结合使用。