javascript - remove - Cancelar una cadena de promesa ECMAScript 6 de vainilla
string to html javascript (13)
¿Hay algún método para borrar los
.then
s de una instancia de JavaScript
Promise
?
He escrito un marco de prueba de JavaScript sobre
QUnit
.
El marco ejecuta pruebas sincrónicamente ejecutando cada una en una
Promise
.
(Perdón por la longitud de este bloque de código. Lo comenté lo mejor que pude, así que se siente menos tedioso).
/* Promise extension -- used for easily making an async step with a
timeout without the Promise knowing anything about the function
it''s waiting on */
$$.extend(Promise, {
asyncTimeout: function (timeToLive, errorMessage) {
var error = new Error(errorMessage || "Operation timed out.");
var res, // resolve()
rej, // reject()
t, // timeout instance
rst, // reset timeout function
p, // the promise instance
at; // the returned asyncTimeout instance
function createTimeout(reject, tempTtl) {
return setTimeout(function () {
// triggers a timeout event on the asyncTimeout object so that,
// if we want, we can do stuff outside of a .catch() block
// (may not be needed?)
$$(at).trigger("timeout");
reject(error);
}, tempTtl || timeToLive);
}
p = new Promise(function (resolve, reject) {
if (timeToLive != -1) {
t = createTimeout(reject);
// reset function -- allows a one-time timeout different
// from the one original specified
rst = function (tempTtl) {
clearTimeout(t);
t = createTimeout(reject, tempTtl);
}
} else {
// timeToLive = -1 -- allow this promise to run indefinitely
// used while debugging
t = 0;
rst = function () { return; };
}
res = function () {
clearTimeout(t);
resolve();
};
rej = reject;
});
return at = {
promise: p,
resolve: res,
reject: rej,
reset: rst,
timeout: t
};
}
});
/* framework module members... */
test: function (name, fn, options) {
var mod = this; // local reference to framework module since promises
// run code under the window object
var defaultOptions = {
// default max running time is 5 seconds
timeout: 5000
}
options = $$.extend({}, defaultOptions, options);
// remove timeout when debugging is enabled
options.timeout = mod.debugging ? -1 : options.timeout;
// call to QUnit.test()
test(name, function (assert) {
// tell QUnit this is an async test so it doesn''t run other tests
// until done() is called
var done = assert.async();
return new Promise(function (resolve, reject) {
console.log("Beginning: " + name);
var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
$$(at).one("timeout", function () {
// assert.fail() is just an extension I made that literally calls
// assert.ok(false, msg);
assert.fail("Test timed out");
});
// run test function
var result = fn.call(mod, assert, at.reset);
// if the test returns a Promise, resolve it before resolving the test promise
if (result && result.constructor === Promise) {
// catch unhandled errors thrown by the test so future tests will run
result.catch(function (error) {
var msg = "Unhandled error occurred."
if (error) {
msg = error.message + "/n" + error.stack;
}
assert.fail(msg);
}).then(function () {
// resolve the timeout Promise
at.resolve();
resolve();
});
} else {
// if test does not return a Promise, simply clear the timeout
// and resolve our test Promise
at.resolve();
resolve();
}
}).then(function () {
// tell QUnit that the test is over so that it can clean up and start the next test
done();
console.log("Ending: " + name);
});
});
}
Si se agota el tiempo de espera de una prueba, mi Promesa de tiempo de espera se
assert.fail()
en la prueba para que la prueba se marque como fallida, lo cual está muy bien, pero la prueba continúa ejecutándose porque la Promesa de prueba (
result
) todavía está esperando para resolverlo
Necesito una buena forma de cancelar mi prueba.
Puedo hacerlo creando un campo en el módulo de marco
this.cancelTest
o algo así y comprobando de vez en cuando (por ejemplo, al comienzo de cada iteración
then()
) en la prueba si cancelar.
Sin embargo, idealmente, podría usar
$$(at).on("timeout", /* something here */)
para borrar los restantes
then()
s en mi variable de
result
, de modo que no se ejecute el resto de la prueba .
Existe algo como esto?
Actualización rápida
Intenté usar
Promise.race([result, at.promise])
.
No funcionó.
Actualización 2 + confusión
Para desbloquearme, agregué algunas líneas con
mod.cancelTest
/ polling dentro de la idea de prueba.
(También eliminé el desencadenante del evento).
return new Promise(function (resolve, reject) {
console.log("Beginning: " + name);
var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
at.promise.catch(function () {
// end the test if it times out
mod.cancelTest = true;
assert.fail("Test timed out");
resolve();
});
// ...
}).then(function () {
// tell QUnit that the test is over so that it can clean up and start the next test
done();
console.log("Ending: " + name);
});
Establecí un punto de interrupción en la declaración
catch
, y está siendo golpeado.
Lo que me confunde ahora es que no se llama a la instrucción
then()
.
Ideas?
Actualización 3
Descubrí lo último.
fn.call()
estaba arrojando un error que no
at.promise.catch()
, por lo que la promesa de prueba fue rechazada antes de que
at.promise.catch()
pudiera resolverlo.
¿Hay algún método para borrar los
.then
s de una instancia de JavaScript Promise?
No. No en ECMAScript 6 al menos. Las promesas (y sus manejadores) no se pueden cancelar de forma predeterminada (desafortunadamente) . Hay un poco de discusión sobre es-debate (por ejemplo, here ) sobre cómo hacer esto de la manera correcta, pero cualquier enfoque que gane no aterrizará en ES6.
El punto de vista actual es que las subclases permitirán crear promesas cancelables usando su propia implementación (no estoy seguro de qué tan bien funcionará) .
Hasta que el comité de idiomas haya descubierto la mejor manera (¿ES7 con suerte?) , Aún puede usar las implementaciones de Promesa de usuario, muchas de las cuales tienen cancelación.
La discusión actual se encuentra en los https://github.com/domenic/cancelable-promise y https://github.com/bergus/promise-cancellation draft.
Aquí está nuestra implementación https://github.com/permettez-moi-de-construire/cancellable-promise
Utilizado como
const {
cancellablePromise,
CancelToken,
CancelError
} = require(''@permettezmoideconstruire/cancellable-promise'')
const cancelToken = new CancelToken()
const initialPromise = SOMETHING_ASYNC()
const wrappedPromise = cancellablePromise(initialPromise, cancelToken)
// Somewhere, cancel the promise...
cancelToken.cancel()
//Then catch it
wrappedPromise
.then((res) => {
//Actual, usual fulfill
})
.catch((err) => {
if(err instanceOf CancelError) {
//Handle cancel error
}
//Handle actual, usual error
})
cual :
- No toca Promise API
-
Hagamos más cancelaciones dentro de
catch
call - Confíe en que se rechace la cancelación en lugar de resolverse a diferencia de cualquier otra propuesta o implementación
Tiradas y comentarios bienvenidos
En realidad, es imposible detener la ejecución de la promesa, pero puede secuestrar el rechazo y llamarlo desde la promesa misma.
function Sleep(ms,cancel_holder) {
return new Promise(function(resolve,reject){
var done=false;
var t=setTimeout(function(){if(done)return;done=true;resolve();}, ms);
cancel_holder.cancel=function(){if(done)return;done=true;if(t)clearTimeout(t);reject();}
})
}
Uso:
function cancelablePromise(cancel_holder,promise_fn,optional_external_cancel) {
if(!cancel_holder)cancel_holder={};
return new Promise( function(resolve,reject) {
var canceled=false;
var resolve2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; resolve.apply(this,arguments);}
var reject2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; reject.apply(this,arguments);}
var on_cancel={}
cancel_holder.cancel=function(){
if(canceled) return; canceled=true;
delete cancel_holder.cancel;
cancel_holder.canceled=true;
if(on_cancel.cancel)on_cancel.cancel();
if(optional_external_cancel)optional_external_cancel();
reject(new Error(''canceled''));
};
return promise_fn.call(this,resolve2,reject2,on_cancel);
});
}
function Sleep(ms,cancel_holder) {
return cancelablePromise(cancel_holder,function(resolve,reject,oncacnel){
var t=setTimeout(resolve, ms);
oncacnel.cancel=function(){if(t)clearTimeout(t);}
})
}
let cancel_holder={};
// meanwhile in another place it can be canceled
setTimeout(function(){ if(cancel_holder.cancel)cancel_holder.cancel(); },500)
Sleep(1000,cancel_holder).then(function() {
console.log(''sleept well'');
}, function(e) {
if(e.message!==''canceled'') throw e;
console.log(''sleep interrupted'')
})
Establezca una propiedad "cancelada" en la Promesa para señalar
then()
y
catch()
para salir temprano.
Es muy efectivo, especialmente en Web Workers que tienen microtask existentes en cola en Promises de los controladores de
onmessage
.
// Queue task to resolve Promise after the end of this script
const promise = new Promise(resolve => setTimeout(resolve))
promise.then(_ => {
if (promise.canceled) {
log(''Promise cancelled. Exiting early...'');
return;
}
log(''No cancelation signaled. Continue...'');
})
promise.canceled = true;
function log(msg) {
document.body.innerHTML = msg;
}
Estoy realmente sorprendido de que nadie mencione
Promise.race
como candidato para esto:
const actualPromise = new Promise((resolve, reject) => { setTimeout(resolve, 10000) });
let cancel;
const cancelPromise = new Promise((resolve, reject) => {
cancel = reject.bind(null, { canceled: true })
})
const cancelablePromise = Object.assign(Promise.race([actualPromise, cancelPromise]), { cancel });
Hay algunas bibliotecas npm para promesas cancelables.
-
https://github.com/sindresorhus/p-cancelable p-cancelable
-
promesa cancelable https://github.com/alkemics/CancelablePromise
La respuesta de @Michael Yagudaev funciona para mí.
Pero la respuesta original no encadenó la promesa envuelta con .catch () para manejar el manejo de rechazos, aquí está mi mejora además de la respuesta de @Michael Yagudaev:
const makeCancelablePromise = promise => {
let hasCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise
.then(val => (hasCanceled ? reject({ isCanceled: true }) : resolve(val)))
.catch(
error => (hasCanceled ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled = true;
}
};
};
// Example Usage:
const cancelablePromise = makeCancelable(
new Promise((rs, rj) => {
/*do something*/
})
);
cancelablePromise.promise.then(() => console.log(''resolved'')).catch(err => {
if (err.isCanceled) {
console.log(''Wrapped promise canceled'');
return;
}
console.log(''Promise was not canceled but rejected due to errors: '', err);
});
cancelablePromise.cancel();
Pruebe con promesa abortable : https://www.npmjs.com/package/promise-abortable
$ npm install promise-abortable
import AbortablePromise from "promise-abortable";
const timeout = new AbortablePromise((resolve, reject, signal) => {
setTimeout(reject, timeToLive, error);
signal.onabort = resolve;
});
Promise.resolve(fn()).then(() => {
timeout.abort();
});
Si bien no hay una forma estándar de hacer esto en ES6, hay una biblioteca llamada Bluebird para manejar esto.
También hay una forma recomendada descrita como parte de la documentación de reacción. Se parece a lo que tiene en sus actualizaciones 2 y 3.
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then((val) =>
hasCanceled_ ? reject({isCanceled: true}) : resolve(val)
);
promise.catch((error) =>
hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
const cancelablePromise = makeCancelable(
new Promise(r => component.setState({...}}))
);
cancelablePromise
.promise
.then(() => console.log(''resolved''))
.catch((reason) => console.log(''isCanceled'', reason.isCanceled));
cancelablePromise.cancel(); // Cancel the promise
Tomado de: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
Si desea evitar que se ejecuten todas las capturas / capturas, puede hacerlo mediante la inyección de una promesa que nunca se resolverá. Probablemente tiene reprocusiones de pérdida de memoria, pero solucionará el problema y no debería causar demasiada memoria desperdiciada en la mayoría de las aplicaciones.
new Promise((resolve, reject) => {
console.log(''first chain link executed'')
resolve(''daniel'');
}).then(name => {
console.log(''second chain link executed'')
if (name === ''daniel'') {
// I don''t want to continue the chain, return a new promise
// that never calls its resolve function
return new Promise((resolve, reject) => {
console.log(''unresolved promise executed'')
});
}
}).then(() => console.log(''last chain link executed''))
// VM492:2 first chain link executed
// VM492:5 second chain link executed
// VM492:8 unresolved promise executed
Si p es una variable que contiene una Promesa, entonces
p.then(empty);
debería descartar la promesa cuando finalmente se complete o si ya está completa (sí, sé que esta no es la pregunta original, pero es mi pregunta).
"vacío" es la
function empty() {}
.
Soy un principiante y probablemente esté equivocado, pero estas otras respuestas parecen demasiado complicadas.
Se supone que las promesas son simples.
versión simple :
simplemente dé la función de rechazo.
class CancelablePromise {
constructor(executor) {
let _reject = null;
const cancelablePromise = new Promise((resolve, reject) => {
_reject = reject;
return executor(resolve, reject);
});
cancelablePromise.cancel = _reject;
return cancelablePromise;
}
}
una solución wraper (fábrica)
La solución que encontré es pasar un objeto cancel_holder. Tendrá una función de cancelación. Si tiene una función de cancelación, entonces es cancelable.
Esta función de cancelación rechaza la promesa con Error (''cancelado'').
Antes de resolver, rechazar o on_cancel impiden que se llame a la función cancelar sin razón.
He encontrado conveniente pasar la acción de cancelación por inyección
const p = new CancelablePromise((resolve, reject) => {
setTimeout(() => {
console.log(''resolved!'');
resolve();
}, 2000);
})
p.catch(console.log);
setTimeout(() => {
p.cancel(new Error(''Fucked up!''));
}, 1000);
const makeCancelable = promise => {
let rejectFn;
const wrappedPromise = new Promise((resolve, reject) => {
rejectFn = reject;
Promise.resolve(promise)
.then(resolve)
.catch(reject);
});
wrappedPromise.cancel = () => {
rejectFn({ canceled: true });
};
return wrappedPromise;
};
Uso:
const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();