如何取消EMCAScript6(香草JavaScript)承诺链
有没有清除JavaScript Promise
实例的.then
的方法?
我在QUnit之上编写了一个JavaScripttesting框架。 该框架通过在Promise
运行每个testing来同步运行testing。 (对不起,这个代码块的长度,我尽我所能评论,所以感觉不那么乏味)
/* 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); }); }); }
如果一个testing超时,我的超时Promise将会在testing中assert.fail()
,这样testing被标记为失败,这一切都很好,但testing继续运行,因为testingPromise( result
)仍在等待解决它。
我需要一个很好的方法来取消我的testing。 我可以通过在框架模块this.cancelTest
上创build一个字段来完成,并且每this.cancelTest
(例如在每个then()
迭代的开始处)检查是否取消。 不过,理想情况下,我可以使用$$(at).on("timeout", /* something here */)
来清除result
variables上剩余的then()
,这样testing的其余部分都不会运行。
有这样的事情吗?
快速更新
我尝试使用Promise.race([result, at.promise])
。 它没有工作。
更新2 +混乱
为了解锁我,我在testing想法中添加了几行mod.cancelTest
/ polling。 (我也删除了事件触发器。)
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); });
我在catch
语句中设置了一个断点,它正在被打中。 现在让我困惑的是, then()
语句没有被调用。 想法?
更新3
想出最后一件事。 fn.call()
抛出了一个我没有捕获的错误,所以testing的承诺是拒绝at.promise.catch()
之前可以解决它。
有没有清除JavaScript Promise实例的
.then
的方法?
不是。至less不在ECMAScript 6中。 承诺(及其处理程序)默认情况下是不可撤销的(不幸的是) 。 关于如何以正确的方式进行讨论(例如这里 ),有一些讨论,但是无论采取哪种方法,都将不会出现在ES6中。
目前的观点是,子类化将允许使用你自己的实现来创build可取消的承诺(不知道这将有多好) 。
直到语言委托人已经find了最好的方法(ES7希望?),你仍然可以使用用户域的承诺实现,其中许多function取消。
目前的讨论是在https://github.com/domenic/cancelable-promise和https://github.com/bergus/promise-cancellation草案。;
虽然在ES6中没有这样做的标准方法,但有一个名为Bluebird的库来处理这个问题。
还有一个build议的方式作为反应文件的一部分。 它看起来类似于你的2和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
取自: https : //facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
如果你想停止执行所有的捕获/捕获,你可以通过注入一个永远不会解决的承诺来做到这一点。 它可能有内存泄漏reprocusions,但它会解决这个问题,不应该在大多数应用程序中造成太多浪费的内存。
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
简单版本 :
只是发出拒绝function。
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();} }) }
包装解决scheme(工厂)
我find的解决scheme是传递一个cancel_holder对象。 它会有一个取消function。 如果它有一个取消function,那么它是可以取消的。
此取消function拒绝承诺错误('取消')。
在parsing,拒绝或on_cancel之前,无法正确调用cancel函数。
我发现方便的方式通过注射取消行动
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') })