then - promise javascript
Promesa: ¿es posible forzar la cancelación de una promesa? (6)
Utilizo ES6 Promises para administrar toda la recuperación de datos de mi red y hay algunas situaciones en las que necesito forzar la cancelación.
Básicamente, el escenario es tal que tengo una búsqueda de escritura anticipada en la interfaz de usuario donde la solicitud se delega al backend tiene que llevar a cabo la búsqueda en función de la entrada parcial. Si bien esta solicitud de red (# 1) puede tomar un poco de tiempo, el usuario continúa escribiendo lo que eventualmente desencadena otra llamada de back-end (# 2)
Aquí el # 2 naturalmente tiene prioridad sobre el # 1, por lo que me gustaría cancelar la solicitud de envoltura de Promesa # 1. Ya tengo un caché de todas las Promesas en la capa de datos, por lo que teóricamente puedo recuperarlo ya que estoy tratando de enviar una Promesa para el n. ° 2.
Pero, ¿cómo cancelo la Promesa n. ° 1 una vez que la recupero del caché?
¿Alguien podría sugerir un enfoque?
No. No podemos hacer eso todavía.
Las promesas de ES6 aún no admiten la cancelación. Está en camino, y su diseño es algo en lo que mucha gente trabajó muy duro. La semántica de cancelación de sonido es difícil de corregir y esto es un trabajo en progreso. Hay debates interesantes sobre el repositorio "fetch", sobre esdiscuss y sobre varios otros repositorios sobre GH, pero sería paciente si fuera usted.
Pero, pero, pero ... ¡la cancelación es realmente importante!
Es, la realidad del asunto es que la cancelación es realmente un escenario importante en la programación del lado del cliente. Los casos que describe como abortar solicitudes web son importantes y están en todas partes.
Entonces ... ¡el idioma me jodió!
Sí, perdón por eso.
Las promesas tenían que entrar primero antes de que se especificaran más cosas, por lo que entraron sin algunas cosas útiles como
.finally
y
.cancel
, sin embargo, está en camino, a la especificación a través del DOM.
La cancelación
no
es una ocurrencia tardía, es solo una limitación de tiempo y un enfoque más iterativo para el diseño de API.
¿Entonces Que puedo hacer?
Tienes varias alternativas:
- Use una biblioteca de terceros como bluebird que puede moverse mucho más rápido que la especificación y, por lo tanto, tener cancelación, así como un montón de otras cosas, esto es lo que hacen las grandes empresas como WhatsApp.
- Pase un token de cancelación.
Usar una biblioteca de terceros es bastante obvio. En cuanto a un token, puede hacer que su método tome una función y luego lo llame, como tal:
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;
});
};
Lo que te dejaría hacer:
var token = {};
var promise = getWithCancel("/someUrl", token);
// later we want to abort the promise:
token.cancel();
Su caso de uso real:
last
Esto no es demasiado difícil con el enfoque de token:
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);
};
}
Lo que te dejaría hacer:
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
});
Y no, las bibliotecas como Bacon y Rx no "brillan" aquí porque son bibliotecas observables, simplemente tienen la misma ventaja que las bibliotecas de promesa de nivel de usuario tienen al no estar vinculadas a las especificaciones. Supongo que esperaremos para ver en ES2016 cuando los observables se vuelvan nativos. Sin embargo, son ingeniosos para escribir a máquina.
Las propuestas estándar para promesas cancelables han fallado.
Una promesa no es una superficie de control para la acción asíncrona que la cumple; confunde propietario con consumidor. En su lugar, cree funciones asincrónicas que puedan cancelarse mediante algún token pasado.
Otra promesa hace un buen token, haciendo que cancelar sea fácil de implementar con
Promise.race
:
Ejemplo:
Use
Promise.race
para cancelar el efecto de una cadena anterior:
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">
Aquí estamos "cancelando" búsquedas anteriores inyectando un resultado
undefined
y probándolo, pero fácilmente podríamos imaginar rechazarlo con
"CancelledError"
.
Por supuesto, esto en realidad no cancela la búsqueda en la red, pero eso es una limitación de la
fetch
.
Si
fetch
tomara una promesa de cancelación como argumento, entonces podría cancelar la actividad de la red.
He
proposed
este "Cancelar patrón de promesa" en es-discusion, exactamente para sugerir que
fetch
haga esto.
Me enfrenté a un problema similar recientemente.
Tenía un cliente basado en promesas (no uno de red) y quería proporcionar siempre los últimos datos solicitados al usuario para mantener la interfaz de usuario sin problemas.
Después de luchar con la idea de cancelación,
Promise.race(...)
y
Promise.all(..)
comencé a recordar mi último ID de solicitud y cuando se cumplió la promesa, solo estaba representando mis datos cuando coincidían con el ID de una última solicitud .
Espero que ayude a alguien.
Revisé la referencia de Mozilla JS y encontré esto:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
Vamos a ver:
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
});
Tenemos aquí p1 y p2 en
Promise.race(...)
como argumentos, esto en realidad está creando una nueva promesa de resolución, que es lo que necesita.
Sugiero usar Promise Extensions para JavaScript (Prex) . Su autor, Ron Buckton, es uno de los ingenieros clave de TypeScript y también es el responsable de la propuesta de cancelación de ECMAScript del TC39 actual. La biblioteca está bien documentada y es probable que Prex cumpla con el estándar.
En una nota personal y proveniente de una gran experiencia en C #, me gusta mucho el hecho de que Prex está modelado sobre el marco existente de
Cancelación en subprocesos administrados
, es decir, basado en el enfoque adoptado con las API .NET de
CancellationTokenSource
/
CancellationToken
.
En mi experiencia, han sido muy útiles para implementar una lógica de cancelación robusta en aplicaciones administradas.
Aquí hay un ejemplo de un retraso con la cancelación, usando
prex.CancellationTokenSource
en Node:
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));
Tenga en cuenta que la cancelación es una carrera.
Es decir, una promesa puede haberse resuelto con éxito, pero para cuando la cumpla (con
await
o en
then
), la cancelación también puede haberse activado.
token.throwIfCancellationRequested()
ti cómo manejas esta carrera, pero nunca está de más llamar a
token.throwIfCancellationRequested()
un tiempo extra, como lo hice anteriormente.
Actualizado
,
amplié la clase Promise
nativa estándar con soporte de cancelación, similar a cómo se
implementa en Bluebird
(es decir, con una devolución de llamada
oncancel
opcional), pero usando
prex.CancellationTokenSource
.
El código para
CancellablePromise
está disponible
here
.
Ver https://www.npmjs.com/package/promise-abortable
$ npm install promise-abortable