我知道有几个问题,但我认为SO中的Q& A都没有提到这个问题中解释的情景。
我有一个JavaScript组件(它是一个KO组件,但它并不重要)。根据此组件中的某些操作,它可以异步加载子组件的几个不同版本。这可能会发生几次。即最初它可以加载子A,然后用子B等替换它。
主要组件需要运行子组件的某些功能。为此,它将一个名为registerApi
的函数传递给子组件构造函数。这样,当子组件完成加载时,它会调用registerApi
。为了避免父级在加载子组件之前调用API,registerApi
以这种方式使用了一个promise:
var childApiDeferred = $.Deferred();
var childApiPromise = childApiDerred.promise();
var registerApi = function(api) {
childApiDeferred.resolve(api);
};
通过这种方式,只要主组件需要调用子组件的API,它就会通过promise来完成,这样,它只会在子组件完成加载并注册其API时运行。子组件API中的方法调用如下:
childApiPromise.then(function(api) { api.method(); })
到目前为止,这么好。问题是,在某个时间点,子组件可以被新组件交换。此时,我需要重置延迟到未解析状态,这样,如果主要组件尝试调用API,则必须等到新组件加载完毕并且已注册其API。
我没有看到任何可重置延迟的实现,也没有任何其他可以解决此问题的方法。我发现的唯一解决方案,它使代码更加复杂(我没有显示它),每当新组件开始加载时,我创建一个新的延迟,并揭示了新的延期,以便调用:
childApiPromise.then(function(api) { api.method(); })
总是提到新的承诺。 (正如我所说,这使代码变得复杂)。
当然,对于第一个子组件,延迟/保证就像一个魅力,但是,我正在寻找一些可以解决的延迟,这样我就可以在新组件开始加载时解析它
是否有一种可重置的延迟或任何其他JavaScript模式,允许存储回调并在新功能准备好时调用它们?是否有可能以任何方式实施它?
正如我在评论中被问到的那样,我会解释它,虽然我认为它与此问题无关。
我正在使用Knockout和组件。我有一个主要组件,它有一个子组件。当我更改包含组件名称的属性时,KO基础结构会自动交换子组件。在这个时间点,我知道我必须"暂停"调用子组件的API,因为正在加载新的子项,此时API不可用。子组件异步加载并接收一系列参数,包括来自主组件的registerApi
回调。子组件完成加载后,会调用registerApi
将其API公开给父组件。 (作为旁注,所有子组件都向主组件公开相同的功能,但具有不同的实现)。
所以,这是交换组件时发生的步骤:
registerApi
回调传递给子组件registerApi
,这也会解析延迟的每次正常工作时删除并创建新承诺。问题是,这需要编写大量代码以确保正确完成并且所有代码都使用新的,而不是旧的延迟或承诺。如果承诺可重置,您只需重置并解决它:两行简单的代码。
答案 0 :(得分:0)
根据定义,Promise只能在一次时完成或拒绝。所以你将无法重置延期的Promise。我建议您的代码结构需要更改才能遵守更多 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,知道只有在API注册后才能解析Promise。
const c = new Child();
c.apiMethod().then(res => {
console.log(res);
});
如果孩子改变或出于任何原因需要重置Promise,你只需将其设置为null,并在下次调用api函数时创建一个新的Promise。
答案 1 :(得分:0)
放手一搏。我没有经过测试,但认为它应该可行。
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
布尔值可能很有用,但仅限于在尚未传递之前删除附加到上一个子的任何回调。如果已经交付,则其Deferred将(很可能)被解析,并且任何附加的回调在执行过程中执行或不可撤销。 Promise不包括撤销附加回调的机制。
如果事实证明kill
选项中没有值,则可以从构造函数中删除它。
很诱人,但不建议写:
myChildLoader(params).then(childAbstract.set());
那个(使用或不使用kill boolean)会导致任何新的.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()
如问题中所述,"每次正常工作时删除并创建新承诺" - 这正是[]
方法的作用。为了确保所有代码都使用新的而不是旧的子代,所写的内容的问题由抽象来处理。