Promise - можно ли принудительно отменить обещание


Я использую обещания ES6 для управления всеми моими сетевыми данными, и есть некоторые ситуации, когда мне нужно заставить их отменить.

в основном сценарий таков, что у меня есть опережающий поиск по типу в пользовательском интерфейсе, где запрос делегируется бэкэнду, должен выполнять поиск на основе частичного ввода. В то время как этот сетевой запрос (#1) может занять немного времени, пользователь продолжает вводить, что в конечном итоге вызывает другой бэкэнд-вызов (#2)

здесь #2 естественно, имеет приоритет над #1, поэтому я хотел бы отменить запрос на обертывание обещания #1. У меня уже есть кэш всех обещаний на уровне данных, поэтому я могу теоретически получить его, когда я пытаюсь представить обещание для #2.

но как я могу отменить обещание #1, Как только я получу его из кэша?

может ли кто-нибудь предложить подход?

3 53

3 ответа:

нет. Мы пока не можем этого сделать.

обещания ES6 не поддерживают отмену и все же. Он уже в пути, и его дизайн-это то, над чем многие люди работали очень тяжело. звук семантика отмены трудно получить право, и это работа в процессе. Есть интересные дебаты по РЕПО" fetch", по esdiscuss и по нескольким другим РЕПО на GH, но я бы просто был терпелив на вашем месте.

но, Но, но.. отмена действительно важно!

это так, реальность дела в том, что отмена есть действительно важный сценарий в программировании на стороне клиента. Случаи, которые вы описываете, как прерывание веб-запросов, важны, и они повсюду.

так... язык меня подвел!

да, извините за это. Обещания должны были попасть в первую очередь, прежде чем дальнейшие вещи были указаны - поэтому они вошли без некоторых полезных вещей, таких как .finally и .cancel - он уже в пути хотя, к спецификации через DOM. Отмена - это не запоздалая мысль это просто ограничение по времени и более итеративный подход к дизайну API.

так что я могу сделать?

у вас есть несколько вариантов:

  • используйте стороннюю библиотеку, например птица кто может двигаться намного быстрее, чем спецификации и, таким образом, имеют отмену, а также кучу других лакомств - это то, что крупные компании, такие как 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, когда наблюдаемые становятся родными. Они are отличный для typeahead, хотя.

стандартные предложения по отмене обещания так и не удалось.

обещание не является управляющей поверхностью для асинхронного действия, выполняющего его; путает владельца с потребителем. Вместо этого создайте асинхронный функции это может быть отменено через какой-то переданный токен.

еще одно обещание делает прекрасный токен, что делает отмену легко реализовать с 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" вместо.

конечно, это на самом деле не отменяет сетевой поиск, но это ограничение fetch. Если fetch должны были принять отмену обещание в качестве аргумента, то он может отменить сетевую активность.

Я предложил это "отменить обещание шаблон" на es-обсудить, именно чтобы предложить, что fetch сделать это.

Я проверил ссылку 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
});

у нас здесь p1, а p2 положить в Promise.race(...) в качестве аргументов это фактически создает новое обещание разрешения, которое вам требуется.