javascript-承诺-是否可以强制取消承诺

我使用ES6 Promises来管理我的所有网络数据检索,并且在某些情况下需要强制取消它们。

基本上,这种情况是这样的:我在UI上进行了预输入搜索,在该UI中,将请求委派给后端必须基于部分输入执行搜索。 尽管此网络请求(#1)可能需要一点时间,但用户继续输入,最终会触发另一个后端调用(#2)

这里#2自然优先于#1,因此我想取消Promise包装请求#1。 我已经在数据层中缓存了所有Promises,因此从理论上讲,我可以在尝试提交#2的Promise时检索它。

但是,一旦从缓存中检索Promise#1,该如何取消呢?

有人可以建议一种方法吗?

Moonwalker asked 2019-10-08T23:18:30Z
5个解决方案
101 votes

不,我们还不能这样做。

ES6 Promise还不支持取消。 它正在进行中,其设计是很多人真正努力的工作。 声音消除的语义很难正确理解,这正在进行中。 关于“获取”回购,esdiscuss以及关于GH的其他几个回购,有一些有趣的辩论,但是如果我是你的话,请耐心等待。

但是,但是,..取消真的很重要!

事实是,取消实际上是客户端编程中的重要场景。 您描述的情况(例如中止Web请求)非常重要,并且无处不在。

所以...语言把我搞砸了!

是的,对此感到抱歉。 在指定进一步的内容之前,承诺必须首先进入-因此他们进入时没有一些有用的东西,例如25608576825383987987和.cancel-它正在通过DOM规范。 取消并不是事后的想法,它只是时间限制,是API设计的一种更迭代的方法。

那我该怎么办?

您有几种选择:

  • 使用诸如bluebird之类的第三方库,该库的运行速度可能比规范快得多,因此具有取消资格以及许多其他好处-这就是WhatsApp之类的大公司所做的。
  • 传递取消令牌。

使用第三方库很明显。 至于令牌,您可以使方法接受一个函数,然后调用它,如下所示:

function getWithCancel(url, token) { // the token is for cancellation
   var xhr = new XMLHttpRequest;
   xhr.open("GET", url);
   return new Promise(function(resolve, reject) {
      xhr.onload = function() { resolve(xhr.responseText); });
      token.cancel = function() {  // SPECIFY CANCELLATION
          xhr.abort(); // abort request
          reject(new Error("Cancelled")); // reject the promise
      };
      xhr.onerror = reject;
   });
};

哪个可以让您做:

var token = {};
var promise = getWithCancel("/someUrl", token);

// later we want to abort the promise:
token.cancel();

您的实际用例-last

使用令牌方法并不难:

function last(fn) {
    var lastToken = { cancel: function(){} }; // start with no op
    return function() {
        lastToken.cancel();
        var args = Array.prototype.slice.call(arguments);
        args.push(lastToken);
        return fn.apply(this, args);
    };
}

哪个可以让您做:

var synced = last(getWithCancel);
synced("/url1?q=a"); // this will get canceled 
synced("/url1?q=ab"); // this will get canceled too
synced("/url1?q=abc");  // this will get canceled too
synced("/url1?q=abcd").then(function() {
    // only this will run
});

不,像Bacon和Rx这样的库在这里不会“发光”,因为它们是可观察的库,它们具有与用户级别承诺库相同的优势,因为它们不受规范约束。 我猜我们将等到可观察性变得原生时在ES2016中看到。 他们是不错的提前输入。

Benjamin Gruenbaum answered 2019-10-08T23:20:16Z
11 votes

可取消承诺的标准提案已失败。

一个承诺不是实现它的异步动作的控制面; 使所有者与消费者混淆。 而是创建可以通过某些传入令牌取消的异步函数。

另一个promise可以作为一个很好的令牌,使fetch的取消操作容易实现:

示例:使用fetch取消前一个链的效果:

fetch
fetch

在这里,我们通过注入fetch结果并对其进行测试来“取消”先前的搜索,但我们可以轻松地想象出fetch拒绝了。

当然,这实际上并不能取消网络搜索,但这是fetch的限制。如果fetch以取消承诺作为参数,则可以取消网络活动。

我已经在es-discuss上提出了这种“取消承诺模式”,恰好建议fetch做到这一点。

jib answered 2019-10-08T23:21:29Z
6 votes

我检查了Mozilla JS参考并发现了这一点:

[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race]

让我们来看看:

var p1 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 500, "one"); 
});
var p2 = new Promise(function(resolve, reject) { 
    setTimeout(resolve, 100, "two"); 
});

Promise.race([p1, p2]).then(function(value) {
  console.log(value); // "two"
  // Both resolve, but p2 is faster
});

我们这里有p1和p2,它们放在Promise.race(...)中作为参数,这实际上是在创建新的解决承诺,这是您所需要的。

nikola-miljkovic answered 2019-10-08T23:22:14Z
2 votes

我建议使用Promise Extensions for JavaScript(Prex)。 它的作者Ron Buckton是TypeScript的关键工程师之一,也是当前TC39的ECMAScript Cancellation提案的支持者。 该库有充分的文档记录,Prex可能会符合标准。

就个人而言,来自C#丰富的背景,我非常喜欢这样一个事实,那就是Prex是以现有的托管线程取消框架为模型的,即基于CancellationTokenSource/oncancel .NET API所采用的方法。 以我的经验,在托管应用程序中实施强大的取消逻辑非常方便。

这是使用Node中的oncancel进行抵消的延迟的示例:

const prex = require('prex');

async function delayWithCancellation(timeoutMs, token) {
  // this can easily be done without async/await,
  // but I believe this linear structure is more readable
  let reg = null;
  try {
    await new Promise((resolve, reject) => {
      const id = setTimeout(resolve, timeoutMs);
      reg = token.register(() => {
        clearTimeout(id);
        reject(new prex.CancelError("delay cancelled."));
      });
    });  
  }
  finally {
    reg && reg.unregister();
  }
}

async function main() {
  const tokenSource = new prex.CancellationTokenSource();
  setTimeout(() => tokenSource.cancel(), 1500); // cancel after 1500ms

  // without cancellation
  await delayWithCancellation(1000, prex.CancellationToken.none);
  console.log("successfully delayed once.");

  // with cancellation
  const token = tokenSource.token;
  await delayWithCancellation(1500, token);
  token.throwIfCancellationRequested();
  console.log("successfully delayed twice."); // we should not be here
}

main().catch(e => console.log(e));

请注意,取消是一场比赛。 即,一个承诺可能已经成功解决,但是当您观察到它(使用oncancelprex.CancellationTokenSource)时,取消触发可能也已触发。 这取决于您如何处理这场比赛,但是像我上面所说的那样,额外打电话给CancellablePromise绝对没有问题。

更新后,我扩展了具有取消支持的标准本机Promise类,类似于在Bluebird中实现的方式(即,使用可选的oncancel回调),但是使用了prex.CancellationTokenSource。此处提供了CancellablePromise的代码。

noseratio answered 2019-10-08T23:23:07Z
0 votes

我最近遇到了类似的问题。

我有一个基于Promise的客户端(不是网络客户端),并且我想始终将最新请求的数据提供给用户,以保持UI流畅。

在努力解决取消想法之后,Promise.race(...)Promise.all(..)我刚刚开始记住我的上一个请求ID,当诺言实现时,我仅在与上一个请求的ID匹配时才呈现我的数据。

希望它可以帮助某人。

Igor Słomski answered 2019-10-08T23:23:48Z
translate from https://stackoverflow.com:/questions/30233302/promise-is-it-possible-to-force-cancel-a-promise