延期承诺或可以重置的类似模式
我知道这已经被问了几次,但我认为SO中的问答都没有涉及这个问题中解释的情况。
我有一个JavaScript组件(它是一个KO组件,无所谓)。 根据此组件中的某些操作,它可以异步加载子组件的几个不同版本。 这可能发生多次。 也就是说,它最初可以加载子A,然后将其替换为子B等等。
主要组件需要运行子组件的一些功能。 为此,它将一个名为registerApi
的函数传递给子组件构造函数。 这样,当子组件完成加载时,它调用registerApi
。 为了避免父组件在加载子组件之前调用API, registerApi
以这种方式使用承诺:
var childApiDeferred = $.Deferred();
var childApiPromise = childApiDerred.promise();
var registerApi = function(api) {
childApiDeferred.resolve(api);
};
通过这种方式,无论何时主组件需要调用子组件的API,它都通过承诺来完成它,因此,它只会在子组件加载并注册其API时运行。 子组件API中的方法如下调用:
childApiPromise.then(function(api) { api.method(); })
到现在为止还挺好。 问题是,在某个时间点,子组件可以被换一个新组件。 此时,我需要将延迟重置为未解析状态,这样,如果主要组件尝试调用API,则必须等待新组件加载并注册其API。
我还没有看到任何可重置延迟的实现,也没有其他可以解决这个问题的事情。 我找到的唯一解决方案,使代码更加复杂(我没有展示它),无论何时一个新组件开始加载,我都会创建一个新的延迟,并展示新延迟的承诺,以便呼叫:
childApiPromise.then(function(api) { api.method(); })
总是指新的新承诺。 (正如我所说,这使代码复杂化)。
当然,对于第一个子组件,延迟/承诺的作用就像是一种魅力,但是,我正在寻找类似延迟的东西,这种延迟可能是未解决的,所以无论何时一个新组件开始加载,我都可以解析它。
是否有一种可重置的延迟或任何其他JavaScript模式,允许存储回调并在新功能准备好时调用它们? 有没有可能以任何方式实现它?
组件交换如何发生?
正如我在评论中被问到的那样,我会解释它,尽管我认为它与这个Q无关。
我正在使用Knockout组件。 我有一个主要组件,它有一个子组件。 当我更改保存组件名称的属性时,KO基础架构会自动更换子组件。 在这个时候,我知道我必须“暂停”对子组件的API的调用,因为正在加载一个新的子组件,此时此API不可用。 子组件异步加载并接收一系列参数,包括来自主组件的registerApi
回调。 当子组件完成加载时,它会调用registerApi
将其API暴露给父组件。 (作为一个侧面说明,所有子组件都向主要组件公开了相同的功能,但具有不同的实现)。
所以,这是交换组件时发生的步骤:
registerApi
回调传递给子组件 registerApi
,它也解析了延迟 每次都能正常工作,删除并创建新的承诺。 问题是这需要编写大量代码以确保正确完成,并且所有代码都使用新的,而不是旧的,延期的或承诺的。 如果可重置的承诺您只需重置并解决它:两行简单的代码。
根据定义,承诺只能实现或拒绝一次 。 所以你将无法重置延期承诺。 我会建议你的代码结构需要改变,以遵守更加Promisey的体系结构。
例如,主要组件对子组件调用的所有函数应该返回Promises。 如果孩子已经加载,那么Promise将立即解决。 像这样的东西;
class Child {
apiMethod() {
return this.waitForApiRegister()
.then(() => {
// do api stuff and return result
return res;
})
},
waitForApiRegister() {
return ((this.apiPromise) || this.apiPromise = $.Deferred());
}
registerApi() {
this.apiPromise.resolve();
}
unregisterApi() {
this.apiPromise = undefined;
}
}
然后,您会将其视为普通的Promise,并知道Promise只有在API注册后才能解决。
const c = new Child();
c.apiMethod().then(res => {
console.log(res);
});
如果孩子发生变化或出于任何原因需要重置Promise,则只需将其设置为null,并在下一次调用api函数时创建一个新的Promise。
给它一个。 我没有测试,但认为它应该工作。
function ApiAbstraction() {
var deferred;
this.promise = null;
this.set = function(kill) {
if(kill) {
deferred.reject('this child never became active'); // This will affect the initial `deferred` and any subsequent `deferred` if not yet resolved.
}
deferred = $.Deferred();
this.promise = deferred.promise();
return deferred.resolve;
};
this.set(); // establish an initial (dummy) deferred (which will never be resolved).
};
首先,创建一个ApiAbstraction的实例:
var childAbstract = new ApiAbstraction();
对于不同类型的儿童,您可以拥有尽可能多的实例,但可能只需要一个实例。
然后,为了加载一个新的孩子(包括初始孩子),我们假设一个异步myChildLoader()
方法 - 那么你有两个选择:
或者:
var registerChild = childAbstract.set();
myChildLoader(params).then(registerChild);
要么 :
var registerChild = childAbstract.set(true);
myChildLoader(params).then(registerChild);
这两个版本都会立即将任何新的.then()等回调附加到新的孩子,即使它尚未交付。 不需要信号量 。
kill
boolean可能很有用,但仅限于在尚未传递的情况下才会杀掉附加到前一个孩子的回调。 如果已经交付,那么其延期将(完全可能)得到解决,并且任何附加的回调要么在执行过程中执行,要么不可撤销地执行。 承诺不包括撤销附加回调的机制。
如果事实证明kill
选项中没有值,则可以从构造函数中删除它。
这很诱人,但不建议写:
myChildLoader(params).then(childAbstract.set());
那个(有或没有kill布尔)会导致任何新的.then处理程序附加到旧的孩子,直到新的孩子变得可用。 但是,代码库中的任何其他类似表达式都会发生危险的竞争。 最后一个交付自己的孩子将赢得比赛(不一定是最后一个叫)。 必须对ApiAbstraction()
进行重大修改以确保不会发生竞争(困难?)。 无论如何,可能是学术的,因为你很可能想尽快切换到新的孩子。
在代码库中的其他地方,您需要通过ApiAbstraction()
实例调用子方法,该实例的范围必须与其直接调用API时child
的方式完全相同,例如:
function doStuffInvolvingChildAPI() {
...
var promise = childAbstract.promise.then(function(child) {
child.method(...);
return child; // make child available down the success path
}); // ... and form a .then chain as required
...
return promise;
}
childAbstract.promise.then(...)
是最糟糕的麻烦。
尽管解释很长,但是这会给你提供你想要交换孩子的“两行简单代码”,即:
var registerChild = childAbstract.set();
myChildLoader(params).then(registerChild);
正如问题中所述,“每次都能正常工作,删除并创建新的承诺” - 这正是.set()
方法的作用。 为了确保所有的代码使用新的而不是老的孩子写什么的问题被抽象所关注。
上一篇: Deferred promise or similar pattern that can be reset
下一篇: Chaining difference in ES6 Promises and PEP3148 Futures