如何在 Javascript Promise 中取消超时?
Posted
技术标签:
【中文标题】如何在 Javascript Promise 中取消超时?【英文标题】:How to cancel timeout inside of 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
【问题讨论】:
你也可以使用装饰器,更多细节在这里:***.com/a/61242606/1691423 【参考方案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;
【讨论】:
以上是关于如何在 Javascript Promise 中取消超时?的主要内容,如果未能解决你的问题,请参考以下文章
如何让 Javascript 在发送请求之前等待 Promise?
如何在JavaScript / TypeScript中等待Promise列表?