Angular 中数组的异步更新

Async update of array in Angular

我有一个渲染推文的视图。在推文中,有 user_mentions 数组。从这个数组中,我想获得一个用户,向 API 发出另一个请求,获取用户的头像,然后呈现它。 视图看起来像:

<md-item ng-repeat="item in content">
  <div class="md-tile-left" ng-if="item.show">
     <img ng-src="{{item.thumb}}" class="face" alt="{{item.name}}">
  </div>
  <div class="md-tile-content">
     {{item.text}}
  </div>
</md-item>

content 变量将包含所有推文。最初,content[i].show 对所有 i 都是假的。控制器代码如下所示:

for(var i = 0; i < data.length; i++) {
  if(data[i].user_mentions[0]) {
     UserAvatar.get(data[i].user_mentions[0].screen_name).then(function(data){
        $scope.content[i].thumb = data.avatar;
        $scope.content[i].show = true;
     });
  }
}

现在,我面临的问题是调用UserAvatar服务后,变量i会继续递增。因此,在到达回调时,我将具有 data.length 的值(因此,将抛出未定义的错误)。我通过将 i 传递给 API 然后将其取回找到了解决方法:

    UserAvatar.get(data[i].user_mentions[0].screen_name, i).then(function(data){
        $scope.content[i].thumb = data.avatar;
        $scope.content[i].show = true;
    });

我意识到这只是让它工作的一种 hack,但是有没有更聪明的方法呢?

问题是您尝试在 for 循环内使用闭包,但它并没有像您发现的那样起作用。您的 hack 是修复它的一种选择。另一种方法是使用 forEach()/map() 或 IIFE ((function(i) { ... })(i);).

将循环包装在一个函数中

在您的示例中,我将执行以下操作:

  var promises = data.map(function(item, index) {
     if (item.user_mentions[0]) {
        return UserAvatar.get(item.user_mentions[0].screen_name)
           .then(function(itemData) {
             $scope.content[index].thumb = itemData.avatar;
             $scope.content[index].show = true;
           });
     } else {
        return $q();
     }
  });
  $q.all(promises).then(function() {
     // All complete
  });

I realise that this is just a hack to make it work, but is there a more clever way of doing it?

正如@MukeshAgarwal 在他的评论中所说,这并不是真正的 hack,因为 UserAvatar.get 是一个异步过程。

因此,您必须在异步调用的时刻捕获 i 的值。

你用的方法还不错,但还有一个方法是使用立即调用的函数:

这个不起作用 :

for (var i = 0; i < 10; i++) {

    // do something async
    setTimeout(function() { 
        console.log(i); // <- This will log 10 each time (the value of i at execution)
    }, 1000)

}

但是这有效

for (var i = 0; i < 10; i++) {

    (function() {      
        var j = i;   // <- The value of i is captured in j. j is in a closure
        // do something async
        setTimeout(function() { 
            console.log(j);  // <- This will log 0, then 1, then 2 ... then 9 
        }, 1000)
    })()

}

无论何时调用一个函数,你都必须记住,当它被执行时,由外部函数创建的范围内的所有变量(不仅是 $scope,还有像 i 这样的外部变量)对该函数都是可见的,我想你已经意识到这一点,重要的是要意识到你实际上并不需要 i.then 的处理程序中你需要获取索引 i 处的内容但是你如何摆脱这种依赖?我提出了两种可能的解决方案:

1)

function getAvatar(name, contentToUpdate) {
  return UserAvatar.get(name)
      .then(function (data) {
        contentToUpdate.thumb = data.avatar;
        contentToUpdate.show = true;
      });
}

for(var i = 0; i < data.length; i++) {
  if(data[i].user_mentions[0]) {
     getAvatar(data[i].user_mentions[0].screen_name, $scope.content[i]);
  }
}

当调用 .then 函数的处理程序时,i 依赖项不再存在

2)

for(var i = 0; i < data.length; i++) {
  if(data[i].user_mentions[0]) {
     UserAvatar.get(data[i].user_mentions[0].screen_name).then((function(content) {
        return function (data) {
          content.thumb = data.avatar;
          content.show = true;
        }
     })($scope.content[i]));
  }
}

在这个解决方案中,一个 IIFE returns 一个将作为处理程序的函数,但 IIFE 实际上会捕获正在分析的内容的值,因此当内部函数返回时(即 onFulfilled .then) 的处理程序被调用没有 i 依赖性

奖励: 一条对我处理函数有很大帮助的规则如下:

A function will be executed in the place it was defined

不记得在哪里看过了,好像是Nicholas Zakas的Professional JavaScript for Web Developers,强烈推荐:)