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

2022-08-30 04:27:57

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

基本上,场景是这样的,我在UI上有一个提前类型搜索,其中请求被委托给后端,必须根据部分输入执行搜索。虽然此网络请求(#1)可能需要一点时间,但用户继续键入,最终触发另一个后端调用(#2)

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

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

任何人都可以提出一种方法吗?


答案 1

在现代 JavaScript 中 - 否

承诺已经解决(呵呵),似乎永远无法取消(待定)承诺。

取而代之的是,有一个跨平台(Node,Browsers等)取消原语作为WHATWG(一个也构建HTML的标准体)的一部分,称为。您可以使用它来取消返回 promise 而不是 promise 本身的函数AbortController

// Take a signal parameter in the function that needs cancellation
async function somethingIWantToCancel({ signal } = {}) {
  // either pass it directly to APIs that support it
  // (fetch and most Node APIs do)
  const response = await fetch('.../', { signal });
  // return response.json;

  // or if the API does not already support it -
  // manually adapt your code to support signals:
  const onAbort = (e) => {
    // run any code relating to aborting here
  };
  signal.addEventListener('abort', onAbort, { once: true });
  // and be sure to clean it up when the action you are performing
  // is finished to avoid a leak
  // ... sometime later ...
  signal.removeEventListener('abort', onAbort);
}

// Usage
const ac = new AbortController();
setTimeout(() => ac.abort(), 1000); // give it a 1s timeout
try {
  await somethingIWantToCancel({ signal: ac.signal });
} catch (e) {
  if (e.name === 'AbortError') {
    // deal with cancellation in caller, or ignore
  } else {
    throw e; // don't swallow errors :)
  }
}

不。我们还不能这样做。

ES6 承诺不支持取消。它正在路上,它的设计是很多人非常努力的事情。声音消除语义很难正确,这是正在进行的工作。关于“获取”存储库,关于esdiscuss和GH上的其他几个存储库,有有趣的辩论,但如果我是你,我会很耐心。

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

事实是,取消确实是客户端编程中的一个重要场景。您描述的中止Web请求等情况很重要,它们无处不在。

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

是的,很抱歉。在指定更多内容之前,承诺必须首先进入 - 所以它们没有一些有用的东西,比如和 - 它正在通过DOM达到规范。.finally.cancel

那我该怎么办呢?

您有几种选择:

  • 使用像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中看到可观察量何时成为原生对象。不过,它们对于打字来说很不错。


答案 2

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

承诺不是实现它的异步操作的控制面;混淆所有者和消费者。相反,请创建可以通过某些传入令牌取消的异步函数

另一个承诺是一个很好的令牌,使取消易于实现: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);
  });
}
Search: <input id="input">

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

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

我在es-discuss上提出了这个“取消承诺模式”,正是为了建议这样做。fetch