使用 _.each 和 $q 承诺迭代小部件

Using _.each and $q promise to iterate widgets

我有一个非常简单的问题:

  1. 使用_.each()迭代一系列仪表板"widgets"。
  2. 调用一个函数来刷新当前小部件,并返回一个 $q 承诺。

现在,我的问题是我希望每次迭代都等待,然后再继续下一次迭代。

我的第一个版本是这个,直到我意识到我需要等待 updateWidget() 完成:

_.each(widgets, function (wid) {
  if (wid.dataModelOptions.linkedParentWidget) {
      updateWidget(wid, parentWidgetData);
  }
});

我的第二个版本是这个,returns 一个承诺。但是,当然,我仍然有迭代继续而不等待的问题:

_.each(widgets, function (wid) {
  if (wid.dataModelOptions.linkedParentWidget) {
    updateWidget(wid, parentWidgetData).then(function(data){
      var i = 1;
    });
  }
});

和 returns 一个 deferred.promise 对象的被调用函数(然后对小部件数据进行服务调用):

function updateWidget(widget, parWidData) {
    var deferred = $q.defer();

    // SAVE THIS WIDGET TO BE REFRESHED FOR THE then() SECTION BELOW 
    $rootScope.refreshingWidget = widget;

    // .. SOME OTHER VAR INITIALIZATION HERE...       
    
    var url = gadgetDataService.prepareAggregationRequest(cubeVectors, aggrFunc, typeName, orderBy, numOrderBy, top, filterExpr, having, drillDown);
    
    return gadgetDataService.sendAggGetRequest(url).then(function (data) {
 var data = data.data[0];
 var widget = {};
 if ($rootScope.refreshingWidget) {       
     widget = $rootScope.refreshingWidget;
 }
 // BUILD KENDO CHART OPTIONS
 var chartOptions = chartsOptionsService.buildKendoChartOptions(data, widget);                

 // create neOptions object, then use jquery extend()
 var newOptions = {};
 $.extend(newOptions, widget.dataModelOptions, chartOptions);
 widget.dataModelOptions = newOptions;

 deferred.resolve(data);
    });
    
    return deferred.promise;
}

对于如何在每次迭代中 "pause" 并在调用的函数完成后继续进行的想法,我将不胜感激。

谢谢, 鲍勃

******* 已更新 ************

我最新版本的迭代代码包括$q.all()如下:

// CREATE ARRAY OF PROMISES !!
var promises = [];
_.each(widgets, function (wid) {
  if (wid.dataModelOptions.linkedParentWidget) {
    promises.push(updateWidget(wid, parentWidgetData));
  }
});
$q.all(promises)
.then(function () {
  $timeout(function () {
    // without a brief timeout, not all Kendo charts will properly refresh.
    $rootScope.$broadcast('childWidgetsRefreshed');
  }, 100);              
});    

我不会尝试在您的 _.each() 中解决每个承诺,而是在您的 _.each 中构建一个承诺数组以获得如下数组:

promises = [gadgetDataService.sendAggGetRequest(url1), gadgetDataService.sendAggGetRequest(url2)....]

然后一次解决所有问题,遍历结果并设置模型:

$q.all(promises).then(function(results){ // iterate through results here })

通过链接承诺

最简单的如下:

var queue = $q.when();
_.each(widgets, function (wid) {
  queue = queue.then(function() {
    if (wid.dataModelOptions.linkedParentWidget) {
      return updateWidget(wid, parentWidgetData);
    }
  });
});
queue.then(function() {
  // all completed sequentially
});

Note: at the end, queue will resolve with the return value of the last iteration

如果你写了很多像这样的异步函数,把它包装成一个实用函数可能会有用:

function eachAsync(collection, cbAsync) {
  var queue = $q.when();
  _.each(collection, function(item, index) {
    queue = queue.then(function() {
      return cbAsync(item, index);
    });
  });
  return queue;
}

// ...
eachAsync(widgets, function(wid) {
  if (wid.dataModelOptions.linkedParentWidget) {
    return updateWidget(wid, parentWidgetData);
  }
}).then(function() {
  // all widgets updated sequentially
  // still resolved with the last iteration
});

这些函数在 "preprocessing" 阶段构建了一个 承诺链 ,因此您的回调会按顺序调用。还有其他方法可以做到,其中一些方法效率更高,占用内存更少,但这个解决方案是最简单的。

通过延迟迭代

此方法甚至会隐藏最后一次迭代的 return 值,并且不会预先构建完整的承诺链。缺点是,它只能用于类似对象的数组。

function eachAsync(array, cbAsync) {
  var index = 0;
  function next() {
    var current = index++;
    if (current < array.length) {
      return $q.when(cbAsync(array[current], current), next);
    }
    // else return undefined
  }
  // This will delay the first iteration as well, and will transform
  // thrown synchronous errors of the first iteration to rejection.
  return $q.when(null, next); 
}

这将遍历 任何可迭代的:

function eachAsync(iterable, cbAsync) {
  var iterator = iterable[Symbol.iterator]();
  function next() {
    var iteration = iterator.next();
    if (!iteration.done) {
      // we do not know the index!
      return $q.when(cbAsync(iteration.value), next);
    } else {
      // the .value of the last iteration treated as final
      // return value
      return iteration.value;
    }
  }
  // This will delay the first iteration as well, and will transform
  // thrown synchronous errors of the first iteration to rejection.
  return $q.when(null, next); 
}

请记住,当集合在迭代过程中发生变化时,这些方法的行为会有所不同。 Promise 链接方法基本上在开始迭代时构建集合的快照(各个值存储在链接回调函数的闭包中),而后者则没有。