如何取消Javascript Promise内部的超时?

时间:2014-08-17 02:06:27

标签: javascript promise settimeout cancellation

我在JavaScript中使用promises并试图宣传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取消超时。什么是最好的方法呢?

BTW没有真正的目的,我只是想知道如何处理这个问题。我也在这里http://plnkr.co/edit/NXFjs1dXWVFNEOeCV1BA?p=preview

4 个答案:

答案 0 :(得分:15)

你可以做什么,你可以从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

答案 1 :(得分:12)

然而,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:传播)本质上是不可能的,因为promises可以随时引入多个dependess和dependees。如果任何家属取消承诺,它将能够干扰未来的家属。有两种方法可以解决这个问题。一种是引入单独的取消“能力”,也许作为论据传递。另一种是引入一种新的抽象,一种可能是可行的“任务”,它可以取代每个任务只有一个观察者(一个随后就可以调用),而不必担心干扰。任务将支持fork()方法来创建新任务,允许另一个dependee保留任务或推迟取消。

答案 2 :(得分:1)

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

答案 3 :(得分:0)

这是我在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);
    });
  }

用法:

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