2

http://codepen.io/rcidaleassumpo/pen/eNEjGY?editors=001 That's the link for the code. Below I first used the forEach to combine the nicknames with the base link, so I could get the fullLink to make the call for with the $http. But then when I tried to make the call, and post into a empty array, things didn't work as I expected, that's why I need your help.

From what I could understand the information that I could get from the $http request, doesn't leave the request itself, and the infos arrays remains empty.

var app  = angular.module('myApp', []);

app.controller('mainController', function($scope, $http){
  $scope.channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","comster404","brunofin","thomasballinger","noobs2ninjas","beohoff"];
  var url = 'https://api.twitch.tv/kraken/channels/';
  $scope.fullLinks = [];
  $scope.infos = [];
  $scope.channels.forEach(function(channel){
      $scope.data = '';
      var link = url + channel + '?callback=JSON_CALLBACK';
      $scope.fullLinks.push(link);
  });
  $scope.fullLinks.forEach(function(link){
      $http.jsonp(link).success(function(data){
         var obj = {}
         obj.url = data.url;
        $scope.infos.push(obj);
      });
  });
  console.log($scope.infos);

});
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Renan Cidale
  • 842
  • 2
  • 10
  • 23

4 Answers4

4

You are trying to log $scope.infos without waiting until requests complete and push loaded response data to array.

The answer here is using Promises for providing callback to fire once all requests resolve and push their respective data:

app.controller('mainController', function($scope, $http, $q) {

    var url = 'https://api.twitch.tv/kraken/channels/';

    $scope.channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx", "RobotCaleb", "comster404", "brunofin", "thomasballinger", "noobs2ninjas", "beohoff"];

    $scope.infos = [];

    $scope.fullLinks = $scope.channels.map(function(channel) {
        return url + channel + '?callback=JSON_CALLBACK';
    });

    $q.all($scope.fullLinks.map(function(url) {
        return $http.jsonp(url).success(function(data) {
            $scope.infos.push({
                url: data.url
            });
        });
    })).then(function() {
        console.log($scope.infos);    
    });

});

or another slightly different variation for $q.all:

$q.all($scope.fullLinks.map($http.jsonp)).then(function(responses) {
    return responses.map(function(response) {
        return {
            url: response.data.url,
            status: response.data.status
        };
    });   
}).then(function(data) {
    $scope.infos = data;
});

To better understand the problem check this very popular question: How do I return the response from an asynchronous call?.

Here is a demo of your code: http://plnkr.co/edit/uhW6eyTMsSvxlLCoHwQ8?p=preview

Community
  • 1
  • 1
dfsq
  • 191,768
  • 25
  • 236
  • 258
1

The problem is that jsonp is asynchronous. The results would be returned to you, but they arrive after you have already called console.log($scope.infos);

You need to make sure you process the results only after each query has finished.

I would solve this using promises.

var promises = $scope.fullLinks.map(function(link){
  return new Promise(function(resolve, reject) {
     console.log('Invoking ' + link);
     $http.jsonp(link).success(function(data){
        console.log('Call to ' + link + 'returned successfully');
        var obj = {}
        obj.url = data.url;
        $scope.infos.push(obj);
        resolve();
     }).
     error(function() {
       // In this example, I'm just failing silently
       // You could also call reject() if you 
       // want the failure to propogate to the caller
       console.log('Call to ' + link + 'failed');
       resolve(); 
     });
  });
});
Promise.all(promises).then(function() {
   console.log('All calls have completed');

   console.log($scope.infos);
});
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
0

You can't expect console.log($scope.infos); will print array with all items because http request are asyncronous and console.log may execute before any http request have returned result

$scope.fullLinks.forEach(function(link){
  $http.jsonp(link).success(function(data){
     var obj = {}
     obj.url = data.url;
     console.log(obj); //will show what request have returned
     $scope.infos.push(obj);
     console.log($scope.infos.length); // will show how array change size
  });

});

you should see that array will grow, everything works

Vladimir Kruglov
  • 196
  • 1
  • 19
0

You need to execute your logic only when you have the response you want from the server. What you're doing now is:

  • request something.
  • do something with it.
  • receive your response from the server.

What you really want is:

  • request something.
  • receive your response from the server.
  • do something with it.

That's why we use Promises. I think it's easier to get what's happening if you move your http requests out to a service.

app
 // create a service for all your http calls
.service('getChannels', function($http) {
  var url = 'https://api.twitch.tv/kraken/channels/'
    , qs = '?callback=JSON_CALLBACK';

  // $http returns a Promise, so if we return $http.get() we will
  // be able to chain the response with .then(), .catch() and .finally()
  this.query = function (channel) {
    return $http.get(url + channel + qs);
  }
})
// inject the service in your controller (see getChannels here below)
.controller('mainController', function($scope, getChannels){

  $scope.channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","comster404","brunofin","thomasballinger","noobs2ninjas","beohoff"];

  $scope.infos = [];

  // angular provides a $q.all method for when you need more 
  // than one response and do something with each one when they 
  // are finished. 
  // We .map the channels and return a Promise for each of them.
  // With "$q.all" and ".then" we know *for sure* that our function
  // will execute only when we have the response in our hands,
  // and never before
  $q.all($scope.channels.map(function (channel) {
    return getChannels.query(channel);
  // handle your response here, "response" will contain all your http responses.
  })).then(function(response) {
    response.forEach()function (data) {
      var obj = {};
      obj.url = data.url;
      $scope.infos.push(obj);
    };
    console.log($scope.infos);

  // remember to take care of errors, ".catch" does just that
  }).catch(handleYourError);


});

It's very important to understand the many (wrong and right) ways to handle promises. Once you get your head around them your controllers will be much lighter and http requests won't be an hassle anymore. Here's what I think is a very easy to follow and thorough article on the matter.

Aurelio
  • 24,702
  • 9
  • 60
  • 63