【问题标题】:How to cancel timeout inside of Javascript Promise?如何在 Javascript Promise 中取消超时?
【发布时间】:2014-10-10 07:51:23
【问题描述】:

我正在玩弄 JavaScript 中的 Promise 并试图 Promisify setTimeout 函数:

function timeout(ms) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('timeout done');
    }, ms);
  }); 
}

var myPromise=timeout(3000); 

myPromise.then(function(result) { 
  console.log(result); // timeout done
})

相当简单,但我想知道如何在承诺解决之前取消我的超时。 timeout 返回 Promise 对象,因此我无法访问 setTimeout 返回的值,并且无法通过 clearTimeout 取消超时。最好的方法是什么?

顺便说一句,这样做没有真正的目的,我只是想知道如何处理。我也在这里http://plnkr.co/edit/NXFjs1dXWVFNEOeCV1BA?p=preview

【问题讨论】:

标签: javascript promise settimeout cancellation


【解决方案1】:

Edit 2021所有平台都已将 AbortController 作为取消原语融合,并且对此有一些内置支持。

在 Node.js 中

// import { setTimeout } from 'timers/promises' // in ESM
const { setTimeout } = require('timers/promises');
const ac = new AbortController();

// cancellable timeout
(async () => {
  await setTimeout(1000, null, { signal: ac.signal });
})();

// abort the timeout, rejects with an ERR_ABORT
ac.abort();

在浏览器中

你可以 polyfill 这个 API 并使用上面的例子:


function delay(ms, value, { signal } = {}) {
    return new Promise((resolve, reject) => {
        const listener = () => {
            clearTimeout(timer);
            reject(new Error('Aborted'));
        };
        const timer = setTimeout(() => {
            signal?.removeEventListener('abort', listener);
            resolve(value);
        }, ms);
        if (signal?.aborted) {
            listener();
        }
        signal?.addEventListener('abort', listener);
    });
}

您可以这样做,您可以从 timeout 函数返回一个取消器,并在需要时调用它。这样您就不需要全局(或在外部范围)存储timeoutid,并且这也可以管理对函数的多次调用。函数timeout 返回的每个对象实例都有自己的取消程序,可以执行取消操作。

function timeout(ms) {
  var timeout, promise;

  promise = new Promise(function(resolve, reject) {
    timeout = setTimeout(function() {
      resolve('timeout done');
    }, ms);
  }); 

  return {
           promise:promise, 
           cancel:function(){clearTimeout(timeout );} //return a canceller as well
         };
}

var timeOutObj =timeout(3000); 

timeOutObj.promise.then(function(result) { 
  console.log(result); // timeout done
});

//Cancel it.
timeOutObj.cancel();

Plnkr

【讨论】:

    【解决方案2】:

    然而,PSL 的答案是正确的 - 有一些注意事项,我会做一些不同的事情。

    • 清除超时意味着代码将不会运行 - 所以我们应该拒绝承诺。
    • 在我们的例子中不需要返回两个东西,我们可以在 JavaScript 中进行猴子补丁。

    这里:

    function timeout(ms, value) {
        var p = new Promise(function(resolve, reject) {
            p._timeout = setTimeout(function() {
                resolve(value);
            }, ms);
            p.cancel = function(err) {
                reject(err || new Error("Timeout"));
                clearTimeout(p._timeout); // We actually don't need to do this since we
                                          // rejected - but it's well mannered to do so
            };
        });
        return p;
    }
    

    我们可以这样做:

    var p = timeout(1500)
    p.then(function(){
         console.log("This will never log");
    })
    
    p.catch(function(){
         console.log("This will get logged so we can now handle timeouts!")
    })
    p.cancel(Error("Timed out"));
    

    人们可能对全面取消感兴趣,并且确实有些库直接支持此功能作为库的一项功能。事实上,我敢说大多数人都这样做。然而,这会导致干扰问题。引用 here 的 KrisKowal 的话:

    我对取消的立场发生了变化。我现在确信 Promise 抽象本质上不可能取消(传播的 bg:),因为 Promise 可以有多个依赖项,并且可以随时引入依赖项。如果任何受抚养人取消承诺,它将能够干扰未来的受抚养人。有两种方法可以解决这个问题。一种是引入单独的取消“能力”,也许作为参数传递。另一个是引入一个新的抽象,一个可能是 thenable 的“Task”,作为交换,要求每个任务只有一个观察者(一个 then call,永远),可以取消而不用担心干扰。任务将支持 fork() 方法来创建新任务,允许另一个依赖者保留任务或推迟取消。

    【讨论】:

    • 虽然我无法在任何地方找到它的文档,但 Promise 的“settler”函数似乎与 var p = new Promise() 在同一事件轮次中运行,因此您不能在内部引用 p定居者。解决方案(至少是我能想到的唯一一个)相当丑陋但有效 - DEMO.
    • 如此固定,这是一个更好的解决方案。
    • 作为引用的附录,kriskowal 提出了更多想法here
    • @Benjamin-Gruenbaum 有更好的答案。如果您将成员添加到 Promise,那么您将无法从依赖 Promise 中取消(即 .then() 结果);更多信息请参见this article
    • 它在超时函数中给出TypeError: Cannot set property '_timeout' of undefined
    【解决方案3】:

    以上对@Benjamin 和@PSL 的回答有效,但是如果您需要外部源使用可取消超时而在内部被取消怎么办?

    例如,交互可能看起来像这样:

    // externally usage of timeout 
    async function() {
      await timeout() // timeout promise 
    } 
    
    // internal handling of timeout 
    timeout.cancel() 
    

    我自己也需要这种实现,所以我想出了以下办法:

    /**
     * Cancelable Timer hack.
     *
     *  @notes
     *    - Super() does not have `this` context so we have to create the timer
     *      via a factory function and use closures for the cancelation data.
     *    - Methods outside the consctutor do not persist with the extended
     *      promise object so we have to declare them via `this`.
     *  @constructor Timer
     */
    function createTimer(duration) {
      let timerId, endTimer
      class Timer extends Promise {
        constructor(duration) {
          // Promise Construction
          super(resolve => {
            endTimer = resolve
            timerId = setTimeout(endTimer, duration)
          })
          // Timer Cancelation
          this.isCanceled = false
          this.cancel = function() {
            endTimer()
            clearTimeout(timerId)
            this.isCanceled = true
          }
        }
      }
      return new Timer(duration)
    }
    

    现在你可以像这样使用计时器了:

    let timeout = createTimer(100)
    

    并在其他地方取消承诺:

     if (typeof promise !== 'undefined' && typeof promise.cancel === 'function') {
      timeout.cancel() 
    }
    

    【讨论】:

      【解决方案4】:

      这是我在 TypeScript 中的答案:

        private sleep(ms) {
          let timerId, endTimer;
          class TimedPromise extends Promise<any> {
            isCanceled: boolean = false;
            cancel = () => {
              endTimer();
              clearTimeout(timerId);
              this.isCanceled = true;
            };
            constructor(fn) {
              super(fn);
            }
          }
          return new TimedPromise(resolve => {
            endTimer = resolve;
            timerId = setTimeout(endTimer, ms);
          });
        }
      

      用法:

      const wait = sleep(10*1000);
      setTimeout(() => { wait.cancel() },5 * 1000);
      await wait; 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-21
        • 2018-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多