承诺 - 是否有可能强制取消承诺

Moo*_*ker 68 javascript promise cancellation

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

基本上这种情况是我在UI上有一个提前输入搜索,其中请求被委托给后端必须基于部分输入执行搜索.虽然此网络请求(#1)可能需要一点时间,但用户会继续键入哪个最终会触发另一个后端调用(#2)

这里#2自然优先于#1,所以我想取消Promise包装请求#1.我已经拥有数据层中所有Promise的缓存,因此我理论上可以检索它,因为我试图提交#2的Promise.

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

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

Ben*_*aum 116

不,我们还不能这样做.

ES6承诺不支持取消还没有.它正在进行中,其设计是许多人非常努力的工作.声音取消语义很难正确,这是正在进行的工作.关于"fetch"回购,esdescuss以及GH上的其他几个回购都有一些有趣的争论,但如果我是你,我会耐心等待.

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

事实上,取消实际上是客户端编程中的一个重要场景.您描述的案例如中止Web请求非常重要,而且它们无处不在.

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

是的,抱歉.在指定更多内容之前,Promise必须首先进入 - 所以他们没有一些有用的东西.finally,.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;
   });
};
Run Code Online (Sandbox Code Playgroud)

哪个会让你这样做:

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

// later we want to abort the promise:
token.cancel();
Run Code Online (Sandbox Code Playgroud)

您的实际使用案例 - 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);
    };
}
Run Code Online (Sandbox Code Playgroud)

哪个会让你这样做:

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
});
Run Code Online (Sandbox Code Playgroud)

不,像Bacon和Rx这样的库在这里没有"闪耀",因为它们是可观察的库,它们只是具有与用户级别承诺库相同的优势,而不受规范约束.我想我们会等到ES2016看到可观测量出现的时候.虽然它们非常适合打字.

  • 本杰明,非常喜欢阅读你的答案.非常深思熟虑,结构清晰,表达清晰,并有很好的实际例子和替代方案.真有帮助.谢谢. (17认同)
  • 我使用带有取消令牌的方法,它确实完成了我需要它做的事情.再次感谢本杰明. (2认同)

jib*_*jib 18

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

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

另一个承诺是一个很好的令牌,使取消易于实现Promise.race:

例如:使用Promise.race取消先前链的影响:

let cancel = () => {};

input.oninput = function(ev) {
  let term = ev.target.value;
  console.log(`searching for "${term}"`);
  cancel();
  let p = new Promise(resolve => cancel = resolve);
  Promise.race([p, getSearchResults(term)]).then(results => {
    if (results) {
      console.log(`results for "${term}"`,results);
    }
  });
}

function getSearchResults(term) {
  return new Promise(resolve => {
    let timeout = 100 + Math.floor(Math.random() * 1900);
    setTimeout(() => resolve([term.toLowerCase(), term.toUpperCase()]), timeout);
  });
}
Run Code Online (Sandbox Code Playgroud)
Search: <input id="input">
Run Code Online (Sandbox Code Playgroud)

在这里,我们通过注入undefined结果并对其进行测试来"取消"以前的搜索,但我们很容易想象拒绝使用"CancelledError".

当然,这实际上并没有取消网络搜索,但这是一个限制fetch.如果fetch要取消取消承诺作为参数,那么它可以取消网络活动.

我在es讨论中提出了这个"取消承诺模式",正好建议这样fetch做.


Art*_*nin 8

使用 AbortController

可以使用 abort 控制器拒绝承诺或根据您的要求解决:

let controller = new AbortController();

let task = new Promise((resolve, reject) => {
  // some logic ...
  controller.signal.addEventListener('abort', () => reject('oops'));
});

controller.abort(); // task is now in rejected state
Run Code Online (Sandbox Code Playgroud)

此外,最好在 abort 时删除事件侦听器以防止内存泄漏

取消 fetch 的工作原理相同:

let controller = new AbortController();
fetch(url, {
  signal: controller.signal
});
Run Code Online (Sandbox Code Playgroud)

或者只是通过控制器:

let controller = new AbortController();
fetch(url, controller);
Run Code Online (Sandbox Code Playgroud)

并调用 abort 方法来取消您传递此控制器的一次或无限次提取 controller.abort();


nik*_*vic 6

我检查了Mozilla JS参考,发现了这个:

https://developer.mozilla.org/en-US/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
});
Run Code Online (Sandbox Code Playgroud)

我们在这里有p1,而p2 Promise.race(...)作为参数输入,这实际上是在创建新的解析承诺,这就是你所需要的.

  • 试了一下.不太好.这解决了最快的承诺...我需要始终解决最新提交的,即无条件取消任何旧的承诺.. (4认同)