2

I am trying to empty, then repopulate an array with the value returned from a promise. However when I do this it doesn't always add them back in the same order.

$scope.$watch ('timeRange', function (newValue, oldValue, scope){
     $scope.chartdata = []
     //If a filter has changed, redraw all charts
     if (newValue !== oldValue)
     {
        for(var i = 0; i< $scope.charts.length; i++){
           $scope.draw($scope.charts[i]).then(function(value){
               $scope.chartdata.push(value);
           });
        }
     }
}, true);

This is being displayed with an ng-repeat.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
Jake McLaughlin
  • 318
  • 1
  • 12
  • Do not use the [tag:promise] tag with AngularJS questions. It attracts answers with ES6 promises. ES6 promises are not integrated with the AngularJS framework. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc. – georgeawg Jun 26 '18 at 16:21
  • 2
    @georgeawg That's not good advice. Aside from some minor naming differences, ES6 promises are virtually analogous with AngularJS promises. There are 606 people following the [promise] tag and 76 following the [angular-promise] tag, so omitting the former makes the question far less visible to people who would be capable of helping. – JLRishe Jun 26 '18 at 17:17

3 Answers3

1

Since you are doing things async the order of resolution might not be guaranteed. You could use index i instead of push

$scope.$watch ('timeRange', function (newValue, oldValue, scope){
     $scope.chartdata = []
     //If a filter has changed, redraw all charts
     if (newValue !== oldValue)
     {
        for(var i = 0; i< $scope.charts.length; i++){
           $scope.draw($scope.charts[i]).then(function(i) { // create scope to capture i
               return function(value) { $scope.chartdata[i] = value; };
           }(i));
        }
     }
}, true);

UPD Example added just to demostrate @georgeawg how scope works

var arr = [1, 2, 3];

for (var i = 0; i < arr.length; i++) {
  setTimeout(function(i) {
    return function() {
      console.log(`Let's teach @georgeawg scopes ${i}`)
    }
  }(i), i * 1000)
}

Or using forEach

$scope.$watch ('timeRange', function (newValue, oldValue, scope){
     $scope.chartdata = []
     //If a filter has changed, redraw all charts
     if (newValue !== oldValue)
     {
        $scope.charts.forEach(function(chart, i) {
          $scope.draw(chart).then(function(value) {
             $scope.chartdata[i] = value;
          })
        })
     }
}, true);

Or add all at once using Promise.all or its angularjs analog $q.all.

$scope.$watch ('timeRange', function (newValue, oldValue, scope){
     $scope.chartdata = []
     //If a filter has changed, redraw all charts
     if (newValue !== oldValue)
     {
        $q.all($scope.charts.map(function(chart) {
          return $scope.draw(chart)
        }).then(function(chartdata) {
          $scope.chartdata = chartdata;
        })
     }
}, true);
Yury Tarabanko
  • 44,270
  • 9
  • 84
  • 98
  • @danh Added `$q.all` code example. Not sure if angular 1 knows about native promises. – Yury Tarabanko Jun 26 '18 at 15:35
  • yes. you may be right. also, my comment makes an error of assigning the compound promise. i'll remove it. – danh Jun 26 '18 at 15:38
  • Avoid `Promise.all`. ES6 promises are not integrated with the AngularJS framework. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc. If `$scope.draw` returns an ES6 promise, convert it to an AngularJS promise with `$q.when`, – georgeawg Jun 26 '18 at 16:12
  • The `for` loop has problems with closures. See [JavaScript closure inside loops – simple practical example](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example). – georgeawg Jun 26 '18 at 16:27
  • @georgeawg "If $scope.draw returns an ES6 promise, ... " this part was working in orignal code. "The for loop has problems with closures." - nope it doesn't, I've specially added a function that captures index. – Yury Tarabanko Jun 26 '18 at 16:30
  • Your answer says "or use forEach". The answer presents a problematic solution as the first choice. – georgeawg Jun 26 '18 at 16:34
  • @georgeawg Read the code. There is even a comment for that. I'm using a function to create a function. – Yury Tarabanko Jun 26 '18 at 16:35
  • Just delete the problematic solutions from the answer. Readers don't always read the comments to see the solutuons presented don't work. – georgeawg Jun 26 '18 at 16:39
  • @georgeawg LOL it works! `i` will be captured in local handler scope. Just read the damn code. `function(i) { retun function(value...` – Yury Tarabanko Jun 26 '18 at 16:40
-1

Snippet with error

function randInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function asyncFunc(index) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(index), randInt(0, 3000));
  });
}

const results = [];

for (var i = 0; i < 5; i++) {
  asyncFunc(i)
    .then((ret) => {
      results.push(ret);
    });
}

setTimeout(() => {
  console.log(results);
}, 4000);

Snippet with soluce

We can pass the position of the data inside of an IEFF function.

function randInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function asyncFunc(index) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(index), randInt(0, 3000));
  });
}

const results = [];

for (var i = 0; i < 5; i++) {
  (function(j) {
    asyncFunc(i)
      .then((ret) => {
        results[j] = ret;
      });
  })(i);
}

setTimeout(() => {
  console.log(results);
}, 4000);
Orelsanpls
  • 22,456
  • 6
  • 42
  • 69
  • ES6 promises are not integrated with the AngularJS framework. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc. – georgeawg Jun 26 '18 at 16:17
  • `setTimeout` is not integrated with the AngularJS framework. Instead use the AngularJS `$timeout` service. – georgeawg Jun 26 '18 at 16:18
  • 1
    @georgeawg The `setTimeout` is here for demonstration purposes. It's not intended to actually be transfered over into AngularJS (OP has no need of `setTimeout` for their scenario). – JLRishe Jun 26 '18 at 17:55
  • Indeed it's here to reproduce the OP situation in a snippet. The interesting part is the IEFF – Orelsanpls Jun 26 '18 at 20:00
-1

A clean way to accomplish this is by using .map and Promise.all (or $q.all() in AngularJS). This will maintain the items' order and as an added benefit, allow you to detect when the full array has been populated.

Borrowing a bit the example code from Gregory's answer:

(Note: The below makes use of setTimeout, new Promise, and Promise.all for the purposes of providing a simple illustrative demonstration as a runnable Stack Snippet. In actual AngularJS code, you would not need the setTimeout or new Promise, and you should use $q.all instead of Promise.all, as shown in the example at the end)

function randInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

function delay(ms) {
  return new Promise(function (resolve) { setTimeout(resolve, ms); });  
}

function asyncFunc(value) {
  return delay(randInt(0, 3000))
  .then(function () { 
      console.log(value, 'finished');
      return value * 2 + 1;
  });
}

const origArray = [0, 1, 2, 3, 4];

Promise.all(origArray.map(asyncFunc))
    .then(function (resultArray) {
      console.log(resultArray);
    });

Applying that to your specific code, we would have:

$scope.$watch ('timeRange', function (newValue, oldValue, scope){
     $scope.chartdata = []
     //If a filter has changed, redraw all charts
     if (newValue !== oldValue)
     {
        $q.all($scope.charts.map($scope.draw))
            .then(function (resultArray) {
                $scope.charts = resultArray;
            });
     }
}, true);
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • ES6 promises are not integrated with the AngularJS framework. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc. – georgeawg Jun 26 '18 at 16:10
  • 1
    @georgeawg I had already mentioned in my answer that one would use `$q.all()` in AngularJS but neglected to do so in the example at the end (force of habit). I've corrected it now. A downvote just for that is a bit harsh, n'est ce pas? – JLRishe Jun 26 '18 at 17:09