可以重置的延期承诺或类似模式

时间:2016-01-14 13:39:18

标签: javascript promise deferred

我知道有几个问题,但我认为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公开给父组件。 (作为旁注,所有子组件都向主组件公开相同的功能,但具有不同的实现)。

所以,这是交换组件时发生的步骤:

  • 主要组件创建一个新的延迟" childApiDeferred",这是未解决的
  • 主要组件设置新子组件的名称,以便ko基础架构交换子组件,并将registerApi回调传递给子组件
  • 子组件是异步加载的,当它完成加载时,会调用registerApi,这也会解析延迟的
  • 父组件可以通过承诺安全地调用子API,因为它们不会被执行直到它被解决

每次正常工作时删除并创建新承诺。问题是,这需要编写大量代码以确保正确完成并且所有代码都使用新的,而不是旧的延迟或承诺。如果承诺可重置,您只需重置并解决它:两行简单的代码。

2 个答案:

答案 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()

如问题中所述,"每次正常工作时删除并创建新承诺" - 这正是[]方法的作用。为了确保所有代码都使用新的而不是旧的子代,所写的内容的问题由抽象来处理。