取消香草 ECMAScript 6 Promise 链
Cancel a vanilla ECMAScript 6 Promise chain
是否有清除 JavaScript Promise
实例的 .then
的方法?
我在 QUnit 之上编写了一个 JavaScript 测试框架。框架 运行s 通过 运行 在 Promise
中对每个进行同步测试。 (很抱歉这个代码块的长度。我尽可能地评论它,所以感觉不那么乏味。)
/* 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);
});
});
}
如果测试超时,我的超时 Promise 将 assert.fail()
测试,以便测试被标记为失败,这一切都很好,但测试继续 运行 因为测试 Promise (result
) 仍在等待解决它。
我需要一种取消测试的好方法。我可以通过在框架模块 this.cancelTest
或其他东西上创建一个字段,并在测试中每隔一段时间(例如在每个 then()
迭代开始时)检查是否取消来做到这一点。但是,理想情况下,我可以使用 $$(at).on("timeout", /* something here */)
清除 result
变量上剩余的 then()
,这样测试的其余部分的 none 就是 运行 .
有这样的东西吗?
快速更新
我尝试使用 Promise.race([result, at.promise])
。没用。
更新 2 + 困惑
为了解锁我,我在测试想法中添加了几行 mod.cancelTest
/轮询。 (我还删除了事件触发器。)
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()
抛出了一个我没有发现的错误,所以在 at.promise.catch()
解决它之前测试承诺被拒绝了。
Is there a method for clearing the .then
s of a JavaScript Promise instance?
没有。至少在 ECMAScript 6 中没有。默认情况下,承诺(及其 then
处理程序)是不可取消的 (不幸的是)。在 es-discuss(例如 here)上有一些关于如何以正确的方式做到这一点的讨论,但无论哪种方法会获胜,它都不会登陆 ES6。
当前的观点是,子类化将允许使用您自己的实现创建可取消的承诺(不确定效果如何)。
在语言委员会找到最佳方法之前(希望是 ES7?) 您仍然可以使用用户空间 Promise 实现,其中许多功能取消。
当前讨论在 https://github.com/domenic/cancelable-promise and https://github.com/bergus/promise-cancellation 草稿中。
虽然在 ES6 中没有执行此操作的标准方法,但有一个名为 Bluebird 的库可以处理此问题。
react 文档中还描述了一种推荐的方法。它看起来与您在第 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
如果你想停止所有 thens/catchs 的执行,你可以通过注入一个永远不会解决的承诺来做到这一点。它可能有内存泄漏反应,但它会解决问题,并且不会在大多数应用程序中造成过多的内存浪费。
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 MyPromise(myparams,cancel_holder) {
return new Promise(function(resolve,reject){
//do work here
cancel_holder.cancel=reject
}
}
或简单的想法2:
function MyPromise() {
var cancel_holder={};
var promise=new Promise(function(resolve,reject){
//do work here
cancel_holder.cancel=reject;
}
promise.cancel=function(){ cancel_holder.cancel(); }
return promise;
}
示例:
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();}
})
}
包装解决方案(工厂)
我找到的解决方案是传递一个 cancel_holder 对象。它将具有取消功能。如果有取消功能就可以取消
此取消函数以错误 ('canceled') 拒绝承诺。
resolve、reject 或on_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')
})
这是我们的实现 https://github.com/permettez-moi-de-construire/cancellable-promise
像这样使用
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
})
其中:
- 不碰 Promise API
- 让我们在
catch
调用中进一步取消
- 依赖取消被拒绝而不是解决不像任何其他提案或实施
欢迎拉取和评论
在 Promise 上设置 "cancelled" 属性 以指示 then()
和 catch()
提前退出。它非常有效,尤其是在 Web Worker 中,现有的微任务在来自 onmessage
个处理程序的 Promises 中排队。
// 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;
}
我真的很惊讶没有人提到 Promise.race
作为候选人:
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 });
@Michael Yagudaev 的回答对我有用。
但是原始答案并没有将包装的承诺与 .catch() 链接起来以处理拒绝处理,这是我在@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();
有一些 npm 库用于可取消的承诺。
如果 p 是一个包含 Promise 的变量,那么 p.then(empty);
应该在 promise 最终完成或已经完成时解除该 promise(是的,我知道这不是最初的问题,但它是我的问题)。 "empty" 是 function empty() {}
。我只是一个初学者,可能是错的,但这些其他答案似乎太复杂了。承诺应该很简单。
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;
};
用法:
const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
尝试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();
});
实际上不可能停止 promise 的执行,但是你可以劫持 reject 并从 promise 本身调用它。
class CancelablePromise {
constructor(executor) {
let _reject = null;
const cancelablePromise = new Promise((resolve, reject) => {
_reject = reject;
return executor(resolve, reject);
});
cancelablePromise.cancel = _reject;
return cancelablePromise;
}
}
用法:
const p = new CancelablePromise((resolve, reject) => {
setTimeout(() => {
console.log('resolved!');
resolve();
}, 2000);
})
p.catch(console.log);
setTimeout(() => {
p.cancel(new Error('Messed up!'));
}, 1000);
可以在 AbortController
的帮助下取消 Promise。
Is there a method for clearing then: yes you can reject the promise with AbortController
object and then the promise
will bypass all then blocks and go directly to the catch block.
示例:
import "abortcontroller-polyfill";
let controller = new window.AbortController();
let signal = controller.signal;
let elem = document.querySelector("#status")
let example = (signal) => {
return new Promise((resolve, reject) => {
let timeout = setTimeout(() => {
elem.textContent = "Promise resolved";
resolve("resolved")
}, 2000);
signal.addEventListener('abort', () => {
elem.textContent = "Promise rejected";
clearInterval(timeout);
reject("Promise aborted")
});
});
}
function cancelPromise() {
controller.abort()
console.log(controller);
}
example(signal)
.then(data => {
console.log(data);
})
.catch(error => {
console.log("Catch: ", error)
});
document.getElementById('abort-btn').addEventListener('click', cancelPromise);
Html
<button type="button" id="abort-btn" onclick="abort()">Abort</button>
<div id="status"> </div>
Note: need to add polyfill, not supported in all browser.
实例
我仍在研究这个想法,但以下是我如何使用 setTimeout
作为示例实现可取消的 Promise。
这个想法是,只要你决定一个承诺就会被解决或拒绝,所以你应该决定何时取消,满足标准,然后调用 reject()
函数你自己。
首先,我认为提早完成承诺有两个原因:完成并完成(我称之为 resolve)和取消(我称之为 reject)。当然,这只是我的感觉。当然有一个 Promise.resolve()
方法,但它在构造函数本身中,并且 return 是一个虚拟的解析承诺。这个实例 resolve()
方法实际上解析了一个实例化的 promise 对象。
其次,您可以在 return 之前愉快地将任何您喜欢的内容添加到新创建的 promise 对象中,所以我刚刚添加了 resolve()
和 reject()
方法使其独立。
第三,诀窍是稍后能够访问执行器 resolve
和 reject
函数,所以我只是将它们存储在闭包中的一个简单对象中.
我认为解决方案很简单,我看不出有什么大问题。
function wait(delay) {
var promise;
var timeOut;
var executor={};
promise=new Promise(function(resolve,reject) {
console.log(`Started`);
executor={resolve,reject}; // Store the resolve and reject methods
timeOut=setTimeout(function(){
console.log(`Timed Out`);
resolve();
},delay);
});
// Implement your own resolve methods,
// then access the stored methods
promise.reject=function() {
console.log(`Cancelled`);
clearTimeout(timeOut);
executor.reject();
};
promise.resolve=function() {
console.log(`Finished`);
clearTimeout(timeOut);
executor.resolve();
};
return promise;
}
var promise;
document.querySelector('button#start').onclick=()=>{
promise=wait(5000);
promise
.then(()=>console.log('I have finished'))
.catch(()=>console.log('or not'));
};
document.querySelector('button#cancel').onclick=()=>{ promise.reject(); }
document.querySelector('button#finish').onclick=()=>{ promise.resolve(); }
<button id="start">Start</button>
<button id="cancel">Cancel</button>
<button id="finish">Finish</button>
如果您的代码放在 class 中,您可以为此使用装饰器。您在 utils-decorators (npm install --save utils-decorators
) 中有这样的装饰器。如果在解析上一个调用之前对该特定方法进行了另一个调用,它将取消对装饰方法的上一个调用。
import {cancelPrevious} from 'utils-decorators';
class SomeService {
@cancelPrevious()
doSomeAsync(): Promise<any> {
....
}
}
或者您可以使用包装函数:
import {cancelPreviousify} from 'utils-decorators';
const cancelable = cancelPreviousify(originalMethod)
https://github.com/vlio20/utils-decorators#cancelprevious-method
使用CPromise package we can use the following approach (Live demo)
import CPromise from "c-promise2";
const chain = new CPromise((resolve, reject, { onCancel }) => {
const timer = setTimeout(resolve, 1000, 123);
onCancel(() => clearTimeout(timer));
})
.then((value) => value + 1)
.then(
(value) => console.log(`Done: ${value}`),
(err, scope) => {
console.warn(err); // CanceledError: canceled
console.log(`isCanceled: ${scope.isCanceled}`); // true
}
);
setTimeout(() => {
chain.cancel();
}, 100);
同样的事情使用 AbortController (Live demo)
import CPromise from "c-promise2";
const controller= new CPromise.AbortController();
new CPromise((resolve, reject, { onCancel }) => {
const timer = setTimeout(resolve, 1000, 123);
onCancel(() => clearTimeout(timer));
})
.then((value) => value + 1)
.then(
(value) => console.log(`Done: ${value}`),
(err, scope) => {
console.warn(err);
console.log(`isCanceled: ${scope.isCanceled}`);
}
).listen(controller.signal);
setTimeout(() => {
controller.abort();
}, 100);
是否有清除 JavaScript Promise
实例的 .then
的方法?
我在 QUnit 之上编写了一个 JavaScript 测试框架。框架 运行s 通过 运行 在 Promise
中对每个进行同步测试。 (很抱歉这个代码块的长度。我尽可能地评论它,所以感觉不那么乏味。)
/* 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);
});
});
}
如果测试超时,我的超时 Promise 将 assert.fail()
测试,以便测试被标记为失败,这一切都很好,但测试继续 运行 因为测试 Promise (result
) 仍在等待解决它。
我需要一种取消测试的好方法。我可以通过在框架模块 this.cancelTest
或其他东西上创建一个字段,并在测试中每隔一段时间(例如在每个 then()
迭代开始时)检查是否取消来做到这一点。但是,理想情况下,我可以使用 $$(at).on("timeout", /* something here */)
清除 result
变量上剩余的 then()
,这样测试的其余部分的 none 就是 运行 .
有这样的东西吗?
快速更新
我尝试使用 Promise.race([result, at.promise])
。没用。
更新 2 + 困惑
为了解锁我,我在测试想法中添加了几行 mod.cancelTest
/轮询。 (我还删除了事件触发器。)
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()
抛出了一个我没有发现的错误,所以在 at.promise.catch()
解决它之前测试承诺被拒绝了。
Is there a method for clearing the
.then
s of a JavaScript Promise instance?
没有。至少在 ECMAScript 6 中没有。默认情况下,承诺(及其 then
处理程序)是不可取消的 (不幸的是)。在 es-discuss(例如 here)上有一些关于如何以正确的方式做到这一点的讨论,但无论哪种方法会获胜,它都不会登陆 ES6。
当前的观点是,子类化将允许使用您自己的实现创建可取消的承诺(不确定效果如何)。
在语言委员会找到最佳方法之前(希望是 ES7?) 您仍然可以使用用户空间 Promise 实现,其中许多功能取消。
当前讨论在 https://github.com/domenic/cancelable-promise and https://github.com/bergus/promise-cancellation 草稿中。
虽然在 ES6 中没有执行此操作的标准方法,但有一个名为 Bluebird 的库可以处理此问题。
react 文档中还描述了一种推荐的方法。它看起来与您在第 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
如果你想停止所有 thens/catchs 的执行,你可以通过注入一个永远不会解决的承诺来做到这一点。它可能有内存泄漏反应,但它会解决问题,并且不会在大多数应用程序中造成过多的内存浪费。
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 MyPromise(myparams,cancel_holder) {
return new Promise(function(resolve,reject){
//do work here
cancel_holder.cancel=reject
}
}
或简单的想法2:
function MyPromise() {
var cancel_holder={};
var promise=new Promise(function(resolve,reject){
//do work here
cancel_holder.cancel=reject;
}
promise.cancel=function(){ cancel_holder.cancel(); }
return promise;
}
示例:
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();}
})
}
包装解决方案(工厂)
我找到的解决方案是传递一个 cancel_holder 对象。它将具有取消功能。如果有取消功能就可以取消
此取消函数以错误 ('canceled') 拒绝承诺。
resolve、reject 或on_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')
})
这是我们的实现 https://github.com/permettez-moi-de-construire/cancellable-promise
像这样使用
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
})
其中:
- 不碰 Promise API
- 让我们在
catch
调用中进一步取消 - 依赖取消被拒绝而不是解决不像任何其他提案或实施
欢迎拉取和评论
在 Promise 上设置 "cancelled" 属性 以指示 then()
和 catch()
提前退出。它非常有效,尤其是在 Web Worker 中,现有的微任务在来自 onmessage
个处理程序的 Promises 中排队。
// 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;
}
我真的很惊讶没有人提到 Promise.race
作为候选人:
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 });
@Michael Yagudaev 的回答对我有用。
但是原始答案并没有将包装的承诺与 .catch() 链接起来以处理拒绝处理,这是我在@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();
有一些 npm 库用于可取消的承诺。
如果 p 是一个包含 Promise 的变量,那么 p.then(empty);
应该在 promise 最终完成或已经完成时解除该 promise(是的,我知道这不是最初的问题,但它是我的问题)。 "empty" 是 function empty() {}
。我只是一个初学者,可能是错的,但这些其他答案似乎太复杂了。承诺应该很简单。
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;
};
用法:
const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
尝试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();
});
实际上不可能停止 promise 的执行,但是你可以劫持 reject 并从 promise 本身调用它。
class CancelablePromise {
constructor(executor) {
let _reject = null;
const cancelablePromise = new Promise((resolve, reject) => {
_reject = reject;
return executor(resolve, reject);
});
cancelablePromise.cancel = _reject;
return cancelablePromise;
}
}
用法:
const p = new CancelablePromise((resolve, reject) => {
setTimeout(() => {
console.log('resolved!');
resolve();
}, 2000);
})
p.catch(console.log);
setTimeout(() => {
p.cancel(new Error('Messed up!'));
}, 1000);
可以在 AbortController
的帮助下取消 Promise。
Is there a method for clearing then: yes you can reject the promise with
AbortController
object and then thepromise
will bypass all then blocks and go directly to the catch block.
示例:
import "abortcontroller-polyfill";
let controller = new window.AbortController();
let signal = controller.signal;
let elem = document.querySelector("#status")
let example = (signal) => {
return new Promise((resolve, reject) => {
let timeout = setTimeout(() => {
elem.textContent = "Promise resolved";
resolve("resolved")
}, 2000);
signal.addEventListener('abort', () => {
elem.textContent = "Promise rejected";
clearInterval(timeout);
reject("Promise aborted")
});
});
}
function cancelPromise() {
controller.abort()
console.log(controller);
}
example(signal)
.then(data => {
console.log(data);
})
.catch(error => {
console.log("Catch: ", error)
});
document.getElementById('abort-btn').addEventListener('click', cancelPromise);
Html
<button type="button" id="abort-btn" onclick="abort()">Abort</button>
<div id="status"> </div>
Note: need to add polyfill, not supported in all browser.
实例
我仍在研究这个想法,但以下是我如何使用 setTimeout
作为示例实现可取消的 Promise。
这个想法是,只要你决定一个承诺就会被解决或拒绝,所以你应该决定何时取消,满足标准,然后调用 reject()
函数你自己。
首先,我认为提早完成承诺有两个原因:完成并完成(我称之为 resolve)和取消(我称之为 reject)。当然,这只是我的感觉。当然有一个
Promise.resolve()
方法,但它在构造函数本身中,并且 return 是一个虚拟的解析承诺。这个实例resolve()
方法实际上解析了一个实例化的 promise 对象。其次,您可以在 return 之前愉快地将任何您喜欢的内容添加到新创建的 promise 对象中,所以我刚刚添加了
resolve()
和reject()
方法使其独立。第三,诀窍是稍后能够访问执行器
resolve
和reject
函数,所以我只是将它们存储在闭包中的一个简单对象中.
我认为解决方案很简单,我看不出有什么大问题。
function wait(delay) {
var promise;
var timeOut;
var executor={};
promise=new Promise(function(resolve,reject) {
console.log(`Started`);
executor={resolve,reject}; // Store the resolve and reject methods
timeOut=setTimeout(function(){
console.log(`Timed Out`);
resolve();
},delay);
});
// Implement your own resolve methods,
// then access the stored methods
promise.reject=function() {
console.log(`Cancelled`);
clearTimeout(timeOut);
executor.reject();
};
promise.resolve=function() {
console.log(`Finished`);
clearTimeout(timeOut);
executor.resolve();
};
return promise;
}
var promise;
document.querySelector('button#start').onclick=()=>{
promise=wait(5000);
promise
.then(()=>console.log('I have finished'))
.catch(()=>console.log('or not'));
};
document.querySelector('button#cancel').onclick=()=>{ promise.reject(); }
document.querySelector('button#finish').onclick=()=>{ promise.resolve(); }
<button id="start">Start</button>
<button id="cancel">Cancel</button>
<button id="finish">Finish</button>
如果您的代码放在 class 中,您可以为此使用装饰器。您在 utils-decorators (npm install --save utils-decorators
) 中有这样的装饰器。如果在解析上一个调用之前对该特定方法进行了另一个调用,它将取消对装饰方法的上一个调用。
import {cancelPrevious} from 'utils-decorators';
class SomeService {
@cancelPrevious()
doSomeAsync(): Promise<any> {
....
}
}
或者您可以使用包装函数:
import {cancelPreviousify} from 'utils-decorators';
const cancelable = cancelPreviousify(originalMethod)
https://github.com/vlio20/utils-decorators#cancelprevious-method
使用CPromise package we can use the following approach (Live demo)
import CPromise from "c-promise2";
const chain = new CPromise((resolve, reject, { onCancel }) => {
const timer = setTimeout(resolve, 1000, 123);
onCancel(() => clearTimeout(timer));
})
.then((value) => value + 1)
.then(
(value) => console.log(`Done: ${value}`),
(err, scope) => {
console.warn(err); // CanceledError: canceled
console.log(`isCanceled: ${scope.isCanceled}`); // true
}
);
setTimeout(() => {
chain.cancel();
}, 100);
同样的事情使用 AbortController (Live demo)
import CPromise from "c-promise2";
const controller= new CPromise.AbortController();
new CPromise((resolve, reject, { onCancel }) => {
const timer = setTimeout(resolve, 1000, 123);
onCancel(() => clearTimeout(timer));
})
.then((value) => value + 1)
.then(
(value) => console.log(`Done: ${value}`),
(err, scope) => {
console.warn(err);
console.log(`isCanceled: ${scope.isCanceled}`);
}
).listen(controller.signal);
setTimeout(() => {
controller.abort();
}, 100);