【问题标题】:$q.all(p1, p2).then() appears to fire before both promises are resolved$q.all(p1, p2).then() 似乎在两个承诺都解决之前触发
【发布时间】:2014-01-14 01:51:47
【问题描述】:

$resource 承诺是否应该与 $q 兼容?也许我用错了。我有两个 $resource 对象,我可能会像这样加载:

$rootScope.foos = Res1.query();    // [ foo1, foo2...]
$rootScope.bars = Res2.query();    // [ bar1, bar2...]

当两个(并且只有两个)查询都到达时,我需要广播一个事件。所以我像这样使用 $q.all:

$q.all([$rootScope.foos.$promise, $rootScope.bars.$promise])
.then(function(){
    // sometimes $rootScope.bars == []
    $rootScope.$broadcast('foos_and_bars!');
});

事件监听器发现 $rootScope.bars 为空/[](一秒钟后它有数据)


更新以响应@Daiwei 和@ExpertSystem

好的,这是 JSFiddle:Reproducing JSFiddle

控制台显示 $q.all() 在 promise 被解决之前被返回。我想要么是使用错误,要么是与 [$resource, $scope, $http, etc.] 中的任何一个有关的角度错误...

【问题讨论】:

  • 可以把相关代码发到Res1.query()吗?
  • 这应该可以正常工作。你能准备一个小提琴或 SSCCE 来重现这个问题吗?
  • @ExpertSysterm 好的,我们开始,很好地重现:jsfiddle.net/mJ9xx/3

标签: javascript angularjs promise


【解决方案1】:

更新:

原来是版本冲突问题。 下面提供的解决方案适用于更高版本(使用 1.2.8 测试),但完全是多余的。
请参阅 Purrell's answer 了解更多信息。


引用 $resource 上的文档:

重要的是要意识到调用 $resource 对象方法会立即返回一个空引用(对象或数组取决于 isArray)。从服务器返回数据后,现有参考将填充实际数据。这是一个有用的技巧,因为通常将资源分配给模型,然后由视图呈现。拥有一个空对象不会导致渲染,一旦数据从服务器到达,那么该对象就会被数据填充,并且视图会自动重新渲染自身以显示新数据。这意味着在大多数情况下,我们不必为操作方法编写回调函数。
[...]
资源实例和集合具有以下附加属性:

  • $promise: ...
  • $resolved: ...

您的 Res1Res2 是 $resource objects(不是 instances)并且(根据文档)调用 query() 方法 "立即返回一个空引用”(在您的情况下是一个数组)。 (UPD:返回的数组仍然具有其他属性。)

正如文档所建议的那样,大多数时候您只需将这些空引用分配给 Scope 并忘记它们(一旦获取数据,模型(以及随后的视图)将自动更新)。


但是,如果您确实有理由希望在数据出现后立即获得明确通知,您可以使用两个“真实”的承诺:

// Create two deferred objects
var deferred1 = $q.defer();
var deferred2 = $q.defer();

// As soon as the data arrives, resolve the deferred objects
$rootScope.foos = Res1.query(function () { deferred1.resolve(); });
$rootScope.bars = Res2.query(function () { deferred2.resolve(); });

// Combine their promises using `$q.all()`
$q.all([deferred1.promise, deferred2.promise]).then(...);

另请参阅此working example


更新

由于返回的数组也有$promise属性,更简洁的版本是:

$rootScope.foos = Res1.query();
$rootScope.bars = Res2.query();

$q.all([$rootScope.foos.$promise, $rootScope.bars.$promise]).then(...);

Working demo

【讨论】:

  • 很好的输入,但我会注意 1) 引用不仅仅是一个空数组。它具有 $promise 和 $resolved 属性(供您使用)。您链接的页面上提到了它。搜索“资源实例和集合具有这些附加属性”。 2) 使用 $resource 公开的 $promise 对象是可以的,这就是它们的用途。 3) 那些 $promise 道具是“真正的”promise,并最终通过 $q 创建。 所以..原来是版本冲突!我的答案空间中的更多注释在这里。我真的很感谢你的时间和想法,非常感谢。
  • 这很奇怪。我(以为我已经)尝试了最新版本,$promise 和 '$resolved` 都未定义。这导致我误解了文档。我现在又试了一次,它就像你说的那样有效。多么阴暗的阴谋:)
【解决方案2】:

这原来是 ngResource 和 Angular 之间的意外版本冲突。 Angular 在 1.2.6 上,但 ngResource 在 1.2.3 上。

最初发布的小提琴不适用于类似但不同的 ngResource 版本问题,即它使用的是旧的 ngResource 版本,该版本不用于公开 $promise 对象(尽管它们仍在幕后使用)。在更高版本的 ngResource 中,promise 被公开,因此它们可以用于这样的事情。

@ExpertSystem 的建议很好,但无法解决问题。我之前确实尝试过 $q.defer() 承诺,并且在存在版本冲突时也有同样的问题。它确实在没有版本冲突的情况下工作,但是没有必要,当它们已经提供给你方便时,不需要创建你自己的promise。

这是working JSFiddleangular 1.2.1,但这对 ngResource 来说已经足够了)

【讨论】:

    【解决方案3】:

    在您的 $q.all()... 之后,您可以使用 .spread(function(prom1, prom2){} 代替在所有承诺返回后运行。按照您当前的操作方式, .then 将触发每次数组中的一项返回时,为什么有时数组中的一项为空。

    【讨论】:

    • $q docs.angularjs.org/api/ng.$q 的文档中似乎没有 .spread 函数。您指的是不同的承诺实现吗?
    • 是的。我的错,this q,我还发现了其他具有传播功能的实现。
    猜你喜欢
    • 1970-01-01
    • 2015-06-23
    • 1970-01-01
    • 2017-12-31
    • 1970-01-01
    • 2020-12-27
    • 1970-01-01
    • 2015-06-24
    • 2019-02-27
    相关资源
    最近更新 更多