Gibt es eine Methode zum Löschen der .then
s einer JavaScript Promise
-Instanz?
Ich habe ein JavaScript-Test-Framework auf QUnit geschrieben. Das Framework führt Tests synchron aus, indem jeder in einer Promise
ausgeführt wird. (Sorry für die Länge dieses Codeblocks. Ich habe es so gut ich kann kommentiert, so dass es weniger langweilig ist.)
/* 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);
});
});
}
Wenn ein Test abgelaufen ist, wird mein Timeout Promise assert.fail()
im Test ausgeführt, so dass der Test als nicht bestanden markiert wird. Dies ist gut und gut. Der Test wird jedoch fortgesetzt, da der Test Promise (result
) noch auf die Auflösung wartet.
Ich brauche einen guten Weg, um meinen Test abzubrechen. Ich kann dies tun, indem ich ein Feld im Framework-Modul this.cancelTest
oder etwas erstellt habe und regelmäßig (z. B. zu Beginn jeder then()
-Iteration) während des Tests prüfe, ob es abgebrochen werden soll. Idealerweise könnte ich jedoch $$(at).on("timeout", /* something here */)
verwenden, um die verbleibenden then()
s auf meiner Variable result
zu löschen, sodass der restliche Test nicht ausgeführt wird.
Gibt es so etwas?
Ich habe es mit Promise.race([result, at.promise])
versucht. Es hat nicht funktioniert.
Um mich zu entsperren, habe ich ein paar Zeilen mit dem mod.cancelTest
/polling in die Testidee eingefügt. (Ich habe auch den Ereignisauslöser entfernt.)
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);
});
Ich habe einen Haltepunkt in der catch
-Anweisung festgelegt, und es wird getroffen. Was mich jetzt verwirrt, ist, dass die then()
-Anweisung nicht aufgerufen wird. Ideen?
Das letzte herausgefunden. fn.call()
hat einen Fehler ausgelöst, den ich nicht erkannt habe. Das Testversprechen wurde abgelehnt, bevor at.promise.catch()
es beheben konnte.
Gibt es eine Methode zum Löschen der
.then
s einer JavaScript Promise-Instanz?
Nein, zumindest nicht in ECMAScript 6. Versprechen (und ihre then
-Handler) sind standardmäßig nicht aufgehoben (leider). Es-Diskussion (z. B. hier ) wird diskutiert, wie dies auf die richtige Art und Weise getan werden kann, aber bei jedem Ansatz wird es nicht in ES6 landen.
Der gegenwärtige Standpunkt ist, dass Subclassing es ermöglicht, stornierbare Versprechen mit Ihrer eigenen Implementierung zu erstellen (nicht sicher, wie gut das funktioniert).
Bis das Sprachkomitee den besten Weg herausgefunden hat (hoffentlich ES7?), Können Sie noch Userland Promise-Implementierungen verwenden, von denen viele Funktionen aufheben.
Aktuelle Diskussionen finden sich in den Entwürfen https://github.com/domenic/cancelable-promise und https://github.com/bergus/promise-cancellation .
In ES6 gibt es keine Standardmethode, aber es gibt eine Bibliothek namens Bluebird , um dies zu handhaben.
Es gibt auch einen empfohlenen Weg, der als Teil der Reaktordokumentation beschrieben wird. Es sieht ähnlich aus, was Sie in Ihren 2. und 3. Updates haben.
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
Entnommen aus: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
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;
};
Verwendungszweck:
const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
Ich bin wirklich überrascht, dass niemand Promise.race
als Kandidaten für dieses Thema erwähnt:
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 });
Es gibt ein paar npm-Bibliotheken für kündbare Versprechen.
p-cancelablehttps://github.com/sindresorhus/p-cancelable
cancelable-promisehttps://github.com/alkemics/CancelablePromise
Es ist tatsächlich unmöglich, die Ausführung des Versprechens zu stoppen, aber Sie können die Ablehnung entführen und sie aus dem Versprechen selbst aufrufen.
class CancelablePromise {
constructor(executor) {
let _reject = null;
const cancelablePromise = new Promise((resolve, reject) => {
_reject = reject;
return executor(resolve, reject);
});
cancelablePromise.cancel = _reject;
return cancelablePromise;
}
}
Verwendungszweck:
const p = new CancelablePromise((resolve, reject) => {
setTimeout(() => {
console.log('resolved!');
resolve();
}, 2000);
})
p.catch(console.log);
setTimeout(() => {
p.cancel(new Error('Fucked up!'));
}, 1000);
einfache Version :
geben Sie einfach die Ablehnungsfunktion aus.
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();}
})
}
eine Wrapperlösung (Fabrik)
die Lösung, die ich gefunden habe, besteht darin, ein cancel_holder-Objekt zu übergeben. es wird eine Abbruchfunktion haben. Wenn es eine Abbruchfunktion hat, kann es abgebrochen werden.
Diese Abbruchfunktion lehnt das Versprechen mit Fehler ab ('storniert').
Bevor auflösen, ablehnen oder on_cancel verhindern, dass die Abbruchfunktion ohne Grund aufgerufen wird.
Ich habe es als zweckmäßig erachtet, die Abbruchaktion durch Injektion zu übergeben
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')
})
Hier ist unsere Implementierung https://github.com/permettez-moi-de-construire/cancellable-promise
Verwendet wie
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
})
welche :
catch
vornehmenZieht und kommentiert willkommen
Versuchen Sie Promise-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();
});
Wenn Sie verhindern möchten, dass alle Zwischenfälle/Fänge ausgeführt werden, können Sie dies tun, indem Sie ein Versprechen einreichen, das niemals gelöst wird. Es hat wahrscheinlich Speicherlecks, aber das Problem wird dadurch behoben, und es sollte in den meisten Anwendungen nicht zu viel Speicherplatz verschwendet werden.
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
@Michael Yagudaevs Antwort funktioniert für mich.
Aber die ursprüngliche Antwort verkettete das umwickelte Versprechen nicht mit .catch (), um die Zurückweisung zu behandeln. Hier ist meine Verbesserung zusätzlich zu @Michael Yagudaevs Antwort:
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();
Legen Sie im Promise eine "stornierte" Eigenschaft fest, um zu signalisieren, dass then()
und catch()
vorzeitig beendet werden. Dies ist besonders effektiv bei Web Workern, bei denen bereits vorhandene Mikrotasks in Promises von onmessage
-Handlern in die Warteschlange gestellt werden.
// 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;
}
Wenn p eine Variable ist, die ein Versprechen enthält, sollte p.then(empty);
das Versprechen verwerfen, wenn es eventuell abgeschlossen ist oder bereits abgeschlossen ist (ja, ich weiß, dass dies nicht die ursprüngliche Frage ist, aber es ist meine Frage). "leer" ist function empty() {}
. Ich bin nur ein Anfänger und wahrscheinlich falsch, aber diese anderen Antworten erscheinen zu kompliziert. Versprechen sollen einfach sein.