How to cancel timeout inside of Javascript Promise?

本小妞迷上赌 提交于 2019-12-31 20:06:30

问题


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

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

Fairly straightforward but I was wondering how would I go about canceling my timeout before the promise resolves. timeout returns Promise object hence I loose access to value that setTimeout returns and cannot cancel timeout via clearTimeout. What woud be the best way to do it?

BTW there is no real purpose for this, I just wonder how this would be approached. Also I plunked it here http://plnkr.co/edit/NXFjs1dXWVFNEOeCV1BA?p=preview


回答1:


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




回答2:


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.




回答3:


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



回答4:


This is my in ts:

  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; 


来源:https://stackoverflow.com/questions/25345701/how-to-cancel-timeout-inside-of-javascript-promise

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!