James Chen James Chen - 15 days ago 4
AngularJS Question

Why the execution order in my controller is not in the right order in AngularJS?

Hi I have made a controller:

app.controller('CalendarCtrl', ['$scope', 'apiService', function($scope, apiService) {

/* event source that contains custom events on the scope */
$scope.events = [];
apiService.get('events').then(function(results) {
$scope.events = results.data;
console.log(results.data);
});
console.log($scope.events);});


I use two
console.log
to check the output and the order of calls. And the
results.data
should return:

[Object]
---> [0]: Object
------> all_day: "1"
------> end: "2016-11-23 10:52:21"
------> id: "1"
------> start: "2016-11-22 10:52:21"
------> title: "Test Event"
------> url: null
---> [1]: Object
...


But in the result, I get my first output as an empty object, which should the output of the second
console.log
. Which means the controller skip the
apiService.get
function and run the rest of the code first.

And then I tried

$scope.events = [];

$scope.events = apiService.get('events').then(function(results) {
return $scope.events = results.data;
});
console.log($scope.events);


But this time, I get:

d {$$state: Object}
---> $$state: Object
------> status: 1
------> value: Array[1]
---------> [0]: Object
------------> all_day: "1"
------------> end: "2016-11-23 10:52:21"
------------> id: "1"
------------> start: "2016-11-22 10:52:21"
------------> title: "Test Event"
------------> url: null


Which warp the results I want inside the
$$state
.

Could anyone tells me why this happen? Thanks.

Answer

What you are seeing is the correct, expected behavior. In your initial code:

app.controller('CalendarCtrl', ['$scope', 'apiService', function($scope, apiService) {

/* event source that contains custom events on the scope */
$scope.events = [];
apiService.get('events').then(function(results) {
    $scope.events = results.data;
    console.log(results.data);
});
console.log($scope.events);});

It is expected that the second console.log($scope.events); will be printed before console.log(results.data);, and that it will print an empty array, which is the value that the scope variable is initialized with. The reason that you should expect that is that apiService.get(...) returns a promise, meaning that the work that will be done by that service method is asynchronous, and only AFTER the result is resolved, will the callback be executed. Because Javascript is single threaded, the application will not block until the promise is fulfilled/rejected, so other code written after the service method will be executed immediately.


In your second snippet:

$scope.events = [];

$scope.events = apiService.get('events').then(function(results) {
    return $scope.events3 = results.data;
});
console.log($scope.events);

you are actually assigning the promise to variable to $scope.events, which is what is being printed. The only reason you see the expected results in the promise's value property is because the promise has already been resolved; had it taken longer to get the events, it would not have been available when the promise was printed to the screen.


So, in summary, because the events are being fetched asynchronously, you cannot expected to print them out in synchronous code since there is no guarantee that they will be resolved by the time the synchronous code is executed. Instead, the correct way is to handle/print them in the promise's success callback:

$scope.events = [];
apiService.get('events').then(function(results) {
    // ASSUMING THE DATA WAS FETCHED SUCCESSFULLY, YOU ARE GUARANTEED TO HAVE IT WHEN THIS CALLBACK IS EXECUTED.
    $scope.events = results.data;
    console.log(results.data);
});