How to cancel timeout inside of Javascript Promise?

前端 未结 4 1653
迷失自我
迷失自我 2020-12-29 06:04

I\'m toying with promises in JavaScript and tried to promisify setTimeout function:

function timeout(ms) {
  return new Promise(function(resolve, reject) {
          


        
相关标签:
4条回答
  • 2020-12-29 06:48

    What you can do it that, you can return a canceller from your timeout function and invoke it when needed. This way you do not need to store the timeoutid globally (or on the outer scope) and also this can manage multiple calls to the function as well. Each instance of the object return by the function timeout will have its own canceler that can perform the cancellation.

    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

    0 讨论(0)
  • 2020-12-29 06:56

    This is my answer in 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);
        });
      }
    

    Usage:

    const wait = sleep(10*1000);
    setTimeout(() => { wait.cancel() },5 * 1000);
    await wait; 
    
    0 讨论(0)
  • 2020-12-29 07:02

    The above to answers by @Benjamin and @PSL work, but what if you need the cancelable timeout to be used by an outside source while being canceled internally?

    For example, the interaction might look somewhat like this:

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

    I needed this kind of implementation myself, so here's what I came up with:

    /**
     * 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)
    }
    

    Now you can use the timer like this:

    let timeout = createTimer(100)
    

    And have the promise canceled somewhere else:

     if (typeof promise !== 'undefined' && typeof promise.cancel === 'function') {
      timeout.cancel() 
    }
    
    0 讨论(0)
  • 2020-12-29 07:04

    PSL's answer is right, however - there are a few caveats and I'd do it a bit differently.

    • A timeout being cleared means the code will not run - so we should reject the promise.
    • Returning two things isn't necessary in our case, we can monkey patch in JavaScript.

    Here:

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

    Which would let us do:

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

    One might be interested in full blown cancellation and indeed some libraries support this directly as a feature of the library. In fact I'd dare say most do. However, this causes interference problems. Quoting KrisKowal from here:

    My position on cancellation has evolved. I am now convinced that cancellation (bg: that propagates) is inherently impossible with the Promise abstraction because promises can multiple dependess and dependees can be introduced at any time. If any dependee cancels a promise, it would be able to interfere with future dependees. There are two ways to get around the problem. One is to introduce a separate cancellation "capability", perhaps passed as an argument. The other is to introduce a new abstraction, a perhaps thenable "Task", which in exchange for requiring that each task only have one observer (one then call, ever), can be canceled without fear of interference. Tasks would support a fork() method to create a new task, allowing another dependee to retain the task or postpone cancellation.

    0 讨论(0)
提交回复
热议问题