【问题标题】:How to write an asynchronous (promise-based) function that times out?如何编写超时的异步(基于承诺)函数?
【发布时间】:2021-04-28 12:03:44
【问题描述】:

我想要一个可以做一些工作的函数,如果某个条件为真,那么就解决 promise。如果没有,请稍等(让我们说一秒钟)然后再试一次。除此之外,如果时间限制到期,promise 将拒绝。我怎么能那样做?看看我的代码示例...如果我将整个函数包装在 return new Promise() 中,我不能在其中使用 await(我需要...如果条件失败,我需要休眠一段时间,而且我还需要等待我在函数结束时等待)。

这是我的代码示例:

async funcWithTimeLimit(numberToDecrement, timeLimit){
    let sleep = undefined;
    let timeLimitTimeout = setTimeout(() => { 
        if (sleep)
            clearTimeout(sleep); 
        return Promise.reject("Time limit of " + (timeLimit/1000) +" secs expired"); //reject
    }, timeLimit);

    while(numberToDecrement > 0){
        for(let i = 10;(i > 0 && numberToDecrement > 0); i--){ //Do some work
            numberToDecrement--;
        }

        if(numberToDecrement > 0){
            await new Promise(resolve => sleep = setTimeout(resolve, 1000));
        }
    }

    clearTimeout(timeLimitTimeout);
    await new Promise((resolve, reject) => sleep = setTimeout(resolve, 500)); // Do something
    return ""; //resolve
}

注意:我最大的问题是 - 我如何编写这个函数以在时间限制到期的情况下捕获拒绝(在我调用 funcWithTimeLimit() 的地方)?

【问题讨论】:

    标签: javascript promise async-await timeout cancellation


    【解决方案1】:

    有两种基本方法。一种是反复检查时间,一旦达到限制就抛出异常,另一种是race each awaited promise against a timeout。第三种方法需要您启动的异步任务的合作,如果它们为您提供一种在其界面中取消它们的方法,以简单地将检查超时的任务卸载给它们。

    在它们中,您都不能通过从setTimeout 抛出异常来拒绝async function

    1. 相当简单:

      function doSomething() {
          return new Promise((resolve, reject) => {
              setTimeout(resolve, 500)); // Do something
          }
      }
      async funcWithTimeLimit(numberToDecrement, timeLimit) {
          while (numberToDecrement > 0) {
              for (let i = 10; i > 0 && numberToDecrement > 0; i--) {
                  if (Date.now() > timelimit) throw new TimeoutError("Exceeded limit");
      //          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  numberToDecrement--; // Do some work
              }
      
              if (numberToDecrement > 0) {
                  if (Date.now() + 1000 > timelimit) throw new TimeoutError("Exceeded limit");
      //          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  await new Promise(resolve => setTimeout(resolve, 1000));
              }
          }
          // but ineffective here:
          await doSomething();
          return "";
      }
      
    2. 赛车:

      async funcWithTimeLimit(numberToDecrement, timeLimit) {
          const timeout = new Promise((resolve, reject) => {
              setTimeout(() => {
                  reject(new TimeoutError("Exceeded limit"));
              }, timeLimit - Date.now());
          });
          timeout.catch(e => void e); // avoid unhandled promise rejection if not needed
          while (numberToDecrement > 0) {
              for (let i = 10; i > 0 && numberToDecrement > 0; i--) {
                  // no guarding of synchronous loops
                  numberToDecrement--; // Do some work
              }
      
              if (numberToDecrement > 0) {
                  await Promise.race([ timeout,
      //          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                      new Promise(resolve => setTimeout(resolve, 1000)),
                  ]);
              }
          }
          await Promise.race([ timeout,
      //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
              doSomething(),
          ]);
          return "";
      }
      

      注意setTimeout 保持事件循环打开,如果函数已经完成,我们只是忽略被拒绝的承诺。更好但更费力的是在我们不再需要超时时取消它:

      async funcWithTimeLimit(numberToDecrement, timeLimit) {
          let timer;
          const timeout = new Promise((resolve, reject) => {
              timer = setTimeout(() => {
                  reject(new TimeoutError("Exceeded limit"));
              }, timeLimit - Date.now());
          });
          try {
              // as before:
              while (numberToDecrement > 0) {
                  for (let i = 10; i > 0 && numberToDecrement > 0; i--) {
                      numberToDecrement--; // Do some work
                  }
      
                  if (numberToDecrement > 0) {
                      await Promise.race([ timeout, new Promise(resolve => setTimeout(resolve, 1000)) ]);
                  }
              }
              await Promise.race([ timeout, doSomething() ]);
              return "";
          } finally {
              clearTimeout(timer);
      //      ^^^^^^^^^^^^^^^^^^^^
          }
      }
      
    3. 与被调用函数合作当然更好:

      function delay(t, limit) {
          if (Date.now() + t > timelimit) throw new TimeoutError("Exceeded limit");
          return new Promise(resolve => setTimeout(resolve, t));
      }
      function doSomething(limit) {
          return new Promise((resolve, reject) => {
              const timeout = setTimeout(() => {
                  xhr.cancel(); // custom API-dependent cancellation
                  reject(new TimeoutError("Exceeded limit"));
              }, limit - Date.now());
              const xhr = someApiCall(); // Do something
              xhr.onerror = err => { clearTimeout(timeout); reject(err); };
              xhr.onload = res => { clearTimeout(timeout); resolve(res); };
          }
      }
      async funcWithTimeLimit(numberToDecrement, timeLimit) {
          while (numberToDecrement > 0) {
              for (let i = 10; i > 0 && numberToDecrement > 0; i--) {
                  numberToDecrement--; // Do some work
              }
      
              if (numberToDecrement > 0) {
                  await delay(1000, timeLimit);
      //                            ^^^^^^^^^
              }
          }
          await doSomething(timeLimit);
      //                    ^^^^^^^^^
          return "";
      }
      

    当然,您可以(在适用的情况下)并且应该(在合理的情况下)结合这些方法。

    【讨论】:

      猜你喜欢
      • 2018-10-11
      • 2020-08-17
      • 2018-07-20
      • 2016-01-08
      • 2019-04-13
      • 2022-01-26
      • 2018-09-07
      • 1970-01-01
      • 2019-02-02
      相关资源
      最近更新 更多