如何中断或取消承诺以清理在 JavaScript/TypeScript 中异步创建的多个对象
How to interrupt or cancel promises in order to clean up multiple objects created asynchronously in JavaScript/TypeScript
注意:我的代码恰好是用 TypeScript 编写的,但是这个问题适用于 TypeScript 和 JavaScript,只是有一些语法上的变化。
我有一个 class 可以订阅一些东西(例如高速公路消息)。当我处理完该对象后,我需要取消订阅所有内容。一般来说,我的代码是这样的:
class Example {
private sub1: Subscription;
private sub2: Subscription;
private sub3: Subscription;
constructor() {
subscribeToThings();
}
async subscribeToThings() {
this.sub1 = await subscribeToThingOne();
this.sub2 = await subscribeToThingTwo();
// *** NOTE 1 ***
this.sub3 = await subscribeToThingThree();
}
cleanupOnDeath () {
this.sub1.unsubscribe();
this.sub2.unsubscribe();
this.sub3.unsubscribe();
}
}
当我不再需要这个对象时,我调用cleanupOnDeath
。
但是,有一个问题。如果订阅(异步的)需要很长时间,则可能会在设置所有 sub1
、sub2
和 sub3
之前调用 "cleanupOnDeath()" 函数。例如,当调用 cleanupOnDeath 时,代码可能位于标记为 *** NOTE 1 ***
的行。
当然,我可以在调用 unsubscribe
、 之前检查每个是否未定义,但这并不能真正解决问题,因为那时第三个订阅得到处理 after cleanupOnDeath
完成,并永远存在。
有没有办法实现此代码,以便可以在 subscribeToThings()
中间调用 cleanupOnDeath()
而不会使代码变得异常复杂?
我能想出的最简单的解决方案是这样的(根据需要替换上面代码的部分):
constructor() {
this.subscribeAndUnsubscribe();
}
async subscribeAndUnsubscribe () {
await this.subscribeToThings();
await cleanupEvent.wait();
this.sub1.unsubscribe();
this.sub2.unsubscribe();
this.sub3.unsubscribe();
}
cleanupOnDeath() {
cleanupEvent.set();
}
其中 cleanupEvent
是某种同步原语。但我不认为这是最佳的,因为它还要求在取消订阅之前完成所有未决订阅。理想情况下,我希望能够提前中止 subscribeToThings()
,而无需在每行代码之后添加检查。例如。可以这样做:
completed:boolean = false;
async subscribeToThings() {
try {
this.abortIfDone();
this.sub1 = await subscribeToThingOne();
this.abortIfDone();
this.sub2 = await subscribeToThingTwo();
this.abortIfDone();
this.sub3 = await subscribeToThingThree();
} catch (abortedEarly) {
// Something here
}
}
abortIfDone() {
if (this.completed) throw 'Something';
}
cleanupOnDeath() {
this.completed = true;
if (this.sub1) this.sub1.unsubscribe();
if (this.sub1) this.sub2.unsubscribe();
if (this.sub1) this.sub3.unsubscribe();
}
但那又乱又复杂。
编辑 - 解决方案
根据@ChrisTavares 的回答,我最终使用了以下代码。这和他的回答之间的区别在于我不再等待任何事情或自己存储订阅。之前,我一直在等待他们获取底层订阅对象,其唯一目的是稍后取消订阅。由于此代码存储承诺并处理使用 then
取消订阅的承诺,因此没有必要这样做。
class Example {
private sub1Promise: Promise<Subscription>;
private sub2Promise: Promise<Subscription>;
private sub3Promise: Promise<Subscription>;
constructor() {
this.subscribeToThings();
}
subscribeToThings() {
// No longer async!
this.sub1Promise = subscribeToThingOne();
this.sub2Promise = subscribeToThingTwo();
this.sub3Promise = subscribeToThingThree();
}
cleanupOnDeath () {
this.sub1Promise.then(s => s.unsubscribe());
this.sub2Promise.then(s => s.unsubscribe());
this.sub3Promise.then(s => s.unsubscribe());
}
}
没有取消承诺的标准方法 - 各个规范委员会都在进行讨论,但这是一个难题,原因有很多,在这里无济于事。
我怀疑这是否是一个合理的问题 - 订阅真的慢到足以担心这个问题吗?我对高速公路不熟悉。
但是,假设是这样,可以做的一件事是不要立即 await
东西,而是坚持实际的承诺。然后你可以在需要的时候添加一个 .then
处理程序来清理东西。像这样:
class Example {
private sub1: Subscription;
private sub2: Subscription;
private sub3: Subscription;
private sub1Promise: Promise<Subscription>;
private sub2Promise: Promise<Subscription>;
private sub3Promise: Promise<Subscription>;
constructor() {
subscribeToThings();
}
async subscribeToThings() {
// *** NOTE - no await here ***
this.sub1Promise = subscribeToThingOne();
this.sub2Promise = subscribeToThingTwo();
this.sub3Promise = subscribeToThingThree();
this.sub1 = await this.sub1Promise;
this.sub2 = await this.sub2Promise;
this.sub3 = await this.sub3Promise;
}
cleanupOnDeath () {
// Unsubscribe each promise. Don't need to check for null,
// they were set in subscribeToThings
this.sub1Promise.then((s) => s.unsubscribe());
this.sub2Promise.then((s) => s.unsubscribe());
this.sub3Promise.then((s) => s.unsubscribe());
}
}
另一种解决方案是通过同步执行器 运行 subscribeToThings 方法的逻辑 nsynjs。它不是基于承诺的,因此无需取消承诺。但它目前使用回调跟踪 运行ning 缓慢的函数,并允许释放被它们占用的资源(例如发出 xhr.abort 或 clearTimeout)以防调用者函数被请求停止。这是如何完成的:
第 1 步。将带有回调的底层函数包装到 nsynjs 感知包装器中。请参阅此示例中的 wait 和 ajaxGetJson 函数:https://github.com/amaksr/nsynjs/blob/master/examples/browser-ajax-seq.js
第 2 步。将订阅逻辑放入单独的函数中:
function syn_SubscribeToThings() {
function subscribeToThingOne() {
...
var data = ajaxGetJson(nsynjsCtx,'subscribe/url1').data;
...
};
function subscribeToThingOneTwo {
...
var data = ajaxGetJson(nsynjsCtx,'subscribe/url2').data;
...
};
function subscribeToThingOneThree {
...
var data = ajaxGetJson(nsynjsCtx,'subscribe/url3').data;
...
};
subscribeToThingOne();
subscribeToThingOneTwo();
subscribeToThingOneThree();
}
第 3 步。运行 通过 nsynjs 函数形成你的 subscribeToThings 方法:
subscribeToThings() {
this.subscribeCtx = nsynjs.run(syn_SubscribeToThings,{},function(){});
}
第 4 步。随时停止:
cleanupOnDeath () {
this.subscribeCtx.stop();
}
它将停止执行 syn_SubscribeToThings 并自动取消所有活动的底层回调。
注意:我的代码恰好是用 TypeScript 编写的,但是这个问题适用于 TypeScript 和 JavaScript,只是有一些语法上的变化。
我有一个 class 可以订阅一些东西(例如高速公路消息)。当我处理完该对象后,我需要取消订阅所有内容。一般来说,我的代码是这样的:
class Example {
private sub1: Subscription;
private sub2: Subscription;
private sub3: Subscription;
constructor() {
subscribeToThings();
}
async subscribeToThings() {
this.sub1 = await subscribeToThingOne();
this.sub2 = await subscribeToThingTwo();
// *** NOTE 1 ***
this.sub3 = await subscribeToThingThree();
}
cleanupOnDeath () {
this.sub1.unsubscribe();
this.sub2.unsubscribe();
this.sub3.unsubscribe();
}
}
当我不再需要这个对象时,我调用cleanupOnDeath
。
但是,有一个问题。如果订阅(异步的)需要很长时间,则可能会在设置所有 sub1
、sub2
和 sub3
之前调用 "cleanupOnDeath()" 函数。例如,当调用 cleanupOnDeath 时,代码可能位于标记为 *** NOTE 1 ***
的行。
当然,我可以在调用 unsubscribe
、 之前检查每个是否未定义,但这并不能真正解决问题,因为那时第三个订阅得到处理 after cleanupOnDeath
完成,并永远存在。
有没有办法实现此代码,以便可以在 subscribeToThings()
中间调用 cleanupOnDeath()
而不会使代码变得异常复杂?
我能想出的最简单的解决方案是这样的(根据需要替换上面代码的部分):
constructor() {
this.subscribeAndUnsubscribe();
}
async subscribeAndUnsubscribe () {
await this.subscribeToThings();
await cleanupEvent.wait();
this.sub1.unsubscribe();
this.sub2.unsubscribe();
this.sub3.unsubscribe();
}
cleanupOnDeath() {
cleanupEvent.set();
}
其中 cleanupEvent
是某种同步原语。但我不认为这是最佳的,因为它还要求在取消订阅之前完成所有未决订阅。理想情况下,我希望能够提前中止 subscribeToThings()
,而无需在每行代码之后添加检查。例如。可以这样做:
completed:boolean = false;
async subscribeToThings() {
try {
this.abortIfDone();
this.sub1 = await subscribeToThingOne();
this.abortIfDone();
this.sub2 = await subscribeToThingTwo();
this.abortIfDone();
this.sub3 = await subscribeToThingThree();
} catch (abortedEarly) {
// Something here
}
}
abortIfDone() {
if (this.completed) throw 'Something';
}
cleanupOnDeath() {
this.completed = true;
if (this.sub1) this.sub1.unsubscribe();
if (this.sub1) this.sub2.unsubscribe();
if (this.sub1) this.sub3.unsubscribe();
}
但那又乱又复杂。
编辑 - 解决方案
根据@ChrisTavares 的回答,我最终使用了以下代码。这和他的回答之间的区别在于我不再等待任何事情或自己存储订阅。之前,我一直在等待他们获取底层订阅对象,其唯一目的是稍后取消订阅。由于此代码存储承诺并处理使用 then
取消订阅的承诺,因此没有必要这样做。
class Example {
private sub1Promise: Promise<Subscription>;
private sub2Promise: Promise<Subscription>;
private sub3Promise: Promise<Subscription>;
constructor() {
this.subscribeToThings();
}
subscribeToThings() {
// No longer async!
this.sub1Promise = subscribeToThingOne();
this.sub2Promise = subscribeToThingTwo();
this.sub3Promise = subscribeToThingThree();
}
cleanupOnDeath () {
this.sub1Promise.then(s => s.unsubscribe());
this.sub2Promise.then(s => s.unsubscribe());
this.sub3Promise.then(s => s.unsubscribe());
}
}
没有取消承诺的标准方法 - 各个规范委员会都在进行讨论,但这是一个难题,原因有很多,在这里无济于事。
我怀疑这是否是一个合理的问题 - 订阅真的慢到足以担心这个问题吗?我对高速公路不熟悉。
但是,假设是这样,可以做的一件事是不要立即 await
东西,而是坚持实际的承诺。然后你可以在需要的时候添加一个 .then
处理程序来清理东西。像这样:
class Example {
private sub1: Subscription;
private sub2: Subscription;
private sub3: Subscription;
private sub1Promise: Promise<Subscription>;
private sub2Promise: Promise<Subscription>;
private sub3Promise: Promise<Subscription>;
constructor() {
subscribeToThings();
}
async subscribeToThings() {
// *** NOTE - no await here ***
this.sub1Promise = subscribeToThingOne();
this.sub2Promise = subscribeToThingTwo();
this.sub3Promise = subscribeToThingThree();
this.sub1 = await this.sub1Promise;
this.sub2 = await this.sub2Promise;
this.sub3 = await this.sub3Promise;
}
cleanupOnDeath () {
// Unsubscribe each promise. Don't need to check for null,
// they were set in subscribeToThings
this.sub1Promise.then((s) => s.unsubscribe());
this.sub2Promise.then((s) => s.unsubscribe());
this.sub3Promise.then((s) => s.unsubscribe());
}
}
另一种解决方案是通过同步执行器 运行 subscribeToThings 方法的逻辑 nsynjs。它不是基于承诺的,因此无需取消承诺。但它目前使用回调跟踪 运行ning 缓慢的函数,并允许释放被它们占用的资源(例如发出 xhr.abort 或 clearTimeout)以防调用者函数被请求停止。这是如何完成的:
第 1 步。将带有回调的底层函数包装到 nsynjs 感知包装器中。请参阅此示例中的 wait 和 ajaxGetJson 函数:https://github.com/amaksr/nsynjs/blob/master/examples/browser-ajax-seq.js
第 2 步。将订阅逻辑放入单独的函数中:
function syn_SubscribeToThings() {
function subscribeToThingOne() {
...
var data = ajaxGetJson(nsynjsCtx,'subscribe/url1').data;
...
};
function subscribeToThingOneTwo {
...
var data = ajaxGetJson(nsynjsCtx,'subscribe/url2').data;
...
};
function subscribeToThingOneThree {
...
var data = ajaxGetJson(nsynjsCtx,'subscribe/url3').data;
...
};
subscribeToThingOne();
subscribeToThingOneTwo();
subscribeToThingOneThree();
}
第 3 步。运行 通过 nsynjs 函数形成你的 subscribeToThings 方法:
subscribeToThings() {
this.subscribeCtx = nsynjs.run(syn_SubscribeToThings,{},function(){});
}
第 4 步。随时停止:
cleanupOnDeath () {
this.subscribeCtx.stop();
}
它将停止执行 syn_SubscribeToThings 并自动取消所有活动的底层回调。