web-dev-qa-db-fra.com

Promesse - est-il possible de forcer l'annulation d'une promesse

J'utilise ES6 Promises pour gérer la récupération de toutes les données de mon réseau. Dans certaines situations, je dois forcer leur annulation.

En gros, le scénario est tel que je dois effectuer une recherche avec saisie anticipée sur l'interface utilisateur où la demande est déléguée au serveur. Elle doit effectuer la recherche en fonction de l'entrée partielle. Bien que cette requête réseau (# 1) puisse prendre un peu de temps, l’utilisateur continue à taper ce qui déclenche éventuellement un autre appel backend (# 2)

Ici, le n ° 2 a naturellement priorité sur le n ° 1 et j'aimerais donc annuler la demande d'emballage n ° 1 de Promise. J'ai déjà un cache de toutes les promesses dans la couche de données afin que je puisse théoriquement le récupérer pendant que je tente de soumettre une promesse pour # 2. 

Mais comment puis-je annuler la promesse n ° 1 une fois que je l'ai extraite de la mémoire cache?

Quelqu'un pourrait-il suggérer une approche?

58
Moonwalker

Non, nous ne pouvons pas encore le faire.

Les promesses de l'ES6 ne prennent pas en charge l'annulation encore. Il est en route et son design est une chose sur laquelle beaucoup de gens ont travaillé très dur. _ {Son} _ la sémantique d'annulation est difficile à comprendre et c'est un travail en cours. Il y a des débats intéressants sur le dépôt "fetch", sur esdiscuss et sur plusieurs autres dépôts sur GH, mais je serais patient si je vous étais.

Mais, mais, mais .. l'annulation est vraiment importante!

En réalité, l’annulation est un {vraiment} scénario important dans la programmation côté client. Les cas que vous décrivez comme l'abandon de demandes Web sont importants et ils sont partout. 

Alors ... la langue m'a foutu!

Oui, désolé pour ça. Les promesses devaient être les premières avant que d'autres éléments ne soient spécifiés - ils sont donc restés sans des éléments utiles tels que .finally et .cancel - ils sont néanmoins en voie de passer aux spécifications par le biais du DOM. L’annulation est une {non} décision après coup, c’est juste une contrainte de temps et une approche plus itérative de la conception des API.

Alors qu'est-ce que je peux faire?

Vous avez plusieurs alternatives:

  • Utilisez une bibliothèque tierce telle que bluebird qui peut se déplacer beaucoup plus rapidement que la spécification et donc annuler ainsi que de nombreux autres bonus - c’est ce que font les grandes entreprises comme WhatsApp.
  • Passer une annulation jeton.

L'utilisation d'une bibliothèque tierce est assez évidente. Comme pour un jeton, vous pouvez faire en sorte que votre méthode prenne une fonction, puis l'appelle comme telle:

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;
   });
};

Ce qui te laisserait faire:

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

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

Votre cas d'utilisation réel - last

Ce n'est pas trop difficile avec l'approche par jeton:

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);
    };
}

Ce qui te laisserait faire:

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
});

Et non, les bibliothèques telles que Bacon et Rx ne "brillent" pas ici car ce sont des bibliothèques observables, elles ont simplement le même avantage que les bibliothèques de promesse de niveau utilisateur, car elles ne sont pas liées aux spécifications. Je suppose que nous attendrons d'avoir dans ES2016 et de voir quand les observables deviendront natives. Ils sont Nifty pour typeahead cependant.

99

Les propositions standard de promesses annulables ont échoué.

Une promesse n'est pas une surface de contrôle pour l'action asynchrone qui la remplit; confond propriétaire avec le consommateur. Créez plutôt fonctions asynchrones qui peuvent être annulés via un jeton transmis.

Une autre promesse est un bon jeton, rendant annulation facile à implémenter avec Promise.race:

Exemple: Utilisez Promise.race pour annuler l'effet d'une chaîne précédente:

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">

Ici, nous "annulons" les recherches précédentes en injectant un résultat undefined et en le testant, mais nous pourrions facilement imaginer le rejet avec "CancelledError" à la place.

Bien sûr, cela n'annule pas réellement la recherche sur le réseau, mais c'est une limitation de fetch. Si fetch devait prendre une promesse d'annulation en argument, alors l'activité réseau pourrait être annulée.

J'ai proposé ce "Annuler modèle de promesse" sur es-discussion, pour suggérer exactement que fetch le fasse.

12
jib

J'ai vérifié la référence de Mozilla JS et trouvé ceci:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race

Regardons ça:

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
});

Nous avons ici p1, et p2 mis dans Promise.race(...) comme arguments, cela crée en fait une nouvelle promesse de résolution, qui correspond à vos besoins.

6
nikola-miljkovic

Voir https://www.npmjs.com/package/promise-abortable

$ npm install promise-abortable
0
Devi

J'ai fait face à un problème similaire récemment.

J'avais un client basé sur une promesse (pas un réseau) et je voulais toujours donner les dernières données demandées à l'utilisateur pour que l'interface utilisateur reste fluide.

Après avoir lutté avec l'idée d'annulation, Promise.race(...) et Promise.all(..), je viens juste de commencer à me souvenir de mon dernier identifiant de demande et lorsque la promesse a été tenue, je ne rendais mes données que si elles correspondaient à l'identifiant d'une dernière demande.

J'espère que ça aide quelqu'un.

0
Igor Słomski