AngularJS:在forEach循环中链接承诺

7

我有些困惑于Promise。我正在使用Google Earth API来进行地址的“巡回演出”。一次“巡回演出”只是一个大约持续一分钟的动画,当一个完成后,下一个应该开始。

这是我的执行“巡回演出”的函数:

var tourAddress = function (address) {
        return tourService.getLatLong(address).then(function (coords) {
            return tourService.getKmlForCoords(coords).then(function (kml) {
                _ge.getTourPlayer().setTour(kml);
                _ge.getTourPlayer().play();

                var counter = 0;
                var d = $q.defer();
                var waitForTour = function () {
                    if (counter < _ge.getTourPlayer().getDuration()) {
                        ++counter;
                        setTimeout(waitForTour, 1000);
                    } else {
                        d.resolve();
                    }
                };

                waitForTour();

                return d.promise;
            });
        });
    }

这似乎运行得相当不错。它启动了动画并返回一个承诺,在动画完成时解决。现在我有一个地址数组,并且我想对它们进行循环遍历:

$scope.addresses.forEach(function (item) {
      tourAddress(item.address).then(function(){
          $log.log(item.address + " complete");
       });
 });

当我这样做时,它们都同时执行(Google Earth对最后一个地址进行动画处理),并且它们都同时完成。如何将它们链接起来,在前一个完成后再触发下一个?
更新:
我使用了@phtrivier的帮助来实现它:
 $scope.addresses.reduce(function (curr,next) {
      return curr.then(function(){
            return tourAddress(next.address)
      });
  }, Promise.resolve()).then(function(){
      $log.log('all complete');
  });
2个回答

6

您说得对,请求是立即完成的,因为调用tourAddress(item.address)会进行请求,并在请求完成时返回一个已解决的Promise。

由于您正在循环调用tourAddress,因此会生成许多Promises,但它们彼此之间没有依赖关系。

您想要的是调用tourAdress,获取返回的Promise,等待它被解决,然后再使用另一个地址调用tourAddress,以此类推。

手动地,您需要编写类似以下内容的代码:

tourAddress(addresses[0])
  .then(function () {
     return tourAddress(addresses[1]);
  })
  .then(function () {
     return tourAddress(addresses[2]);
  })
  ... etc...

如果你想自动完成这个任务(你不是第一个: 如何按顺序执行Promise数组?),你可以尝试将地址列表缩减为单个Promise,该Promise将执行整个链。
_.reduce(addresses, function (memo, address) {

   return memo.then(function (address) {
         // This returns a Promise
         return tourAddress(address);   
   });

}, Promise.resolve());

(这是使用underscore和bluebird的伪代码,但它应该是可适应的)


1
谢谢,我想reduce就是我要找的。我会看看能否让它正常工作。 - Jonesopolis
2
为什么要使用_.reduce?你同样可以使用addresses.reduce - Benjamin Gruenbaum
1
@BenjaminGruenbaum 我不确定$scope.addresses的类型,以及是否在所有地方都支持Array.reduce(尽管似乎是:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) - phtrivier

0

这是因为你正在异步地执行操作,所以它们将同时运行。这个答案应该会帮助你重写循环,使得每个地址在下一个地址之后运行。

jQuery Deferreds(承诺)的异步循环


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接