$http promise chain running in wrong order
Asked Answered
B

1

6

I am new to angularjs. My goal is very simple. I want to make an ajax call to get data and, once complete, I want to make a second call to get another set of data that is dependent on information in the first set.

I'm trying to do this utilizing promise mechanisms so that I can utilize chaining instead of nested ajax calls and to better retain the ability to have independent functions that I can tie together as needed.

My code resembles the following:

var promiseGetWorkTypes = function ($q, $scope, $http) {
	console.log("promiseGetWorkTypes");

	return $q(function (resolve, reject) {
		$http({
			method: 'GET',
			url: '/WorkTypes'
		}).then(
			function (payload) {
				console.log("Got workttypegroups")
				console.log(payload);

				$scope.WorkTypeGroups = payload.data;

				console.log("End of worktypegroups");
				resolve(payload);
			},
			function (payload) {
				reject(payload);
			});
	});
};

var promiseGetRecentActivities = function ($q, $scope, $http) {
	console.log("promiseGetRecentActivities");

	return $q(function (resolve, reject) {
		$http({
			method: 'GET',
			url: '/RecentHistory'
		}).then(
			function (payload) {
				$scope.RecentActivities = payload.data;

				resolve(payload);
			},
			// data contains the response
			// status is the HTTP status
			// headers is the header getter function
			// config is the object that was used to create the HTTP request
			function (payload) {
				reject(payload);
			});
	});
};

var index = angular.module("index", []);

index
.controller('EntitiesController', function ($scope, $http, $timeout, $q) {
	promiseGetWorkTypes($q, $http, $scope)
		.then(promiseGetRecentActivities($q, $http, $scope));
}

However, when I look at my debug console, I see that the call to "promiseGetRecentActivities" is beginning before the call the Ajax handling has occurred for "promiseGetWorkTypes".

What am I missing or doing wrong here?

Booze answered 8/2, 2015 at 3:53 Comment(4)
use $q.all jsfiddle.net/ThomasBurleson/QqKukNeville
No, not $q.all(). OP wants to make the two ajax calls in series.Jerriejerrilee
#23804243Managua
@BenjaminGruenbaum, your link concerns antipatterns with promises and identifies unnecessary use of deferred promises. My code does contain unnecessary complexity since $http will return a promise which doesn't need to be wrapped by $q. This came about because I had previously thought that might be why my original promise from the $http call wasn't working. Obviously that wasn't the case. I have modified my actual project and I just want to make sure anyone who reads this knows that the use of $q in this context was unnecessary and to be avoided. THANK YOU for pointing this out.Booze
S
7

When you write

promiseGetWorkTypes($q, $http, $scope).then(promiseGetRecentActivities($q, $http, $scope));

the promiseGetActivites is called at the time this line is evaluated. You should be able to wrap the call to promiseGetActivities in another function to delay the call until the first promise has resolved to get the calls to run in sequence:

promiseGetWorkTypes($q, $http, $scope).then(function() {
  promiseGetRecentActivities($q, $http, $scope);
});

The reason isn't anything to do with what happens inside then, but due to Javascript syntax. The following:

myFunc1(myFunc2());

passes to myFunc1 the result of calling myFunc2(), and not a reference to the myFunc2 function. So logically, myFunc2 would have to run before myFunc1. If you wrote

myFunc1(myFunc2);

then myFunc1 will receive a reference to myFunc2, and so myFunc1 would run before myFunc2 (and in fact, myFunc2 will only run if somewhere inside myFunc1 there is code that calls it).

Defining a function inline/anonymously doesn't change this behaviour. To pass a result of an anonymous function to another function you can do the following

myFunc1((function() {
  return 'something';
})());

which will evaluate the anonymous function first, as its return value, 'something' will be passed to myFunc1. To pass a reference to an anonymous function you can do the following:

myFunc1(function() {
  return 'something';
});

and then it will be up to myFunc1 whether it will ever call the function passed to it.

Bringing it back to your question, your code:

promiseGetWorkTypes($q, $http, $scope).then(promiseGetRecentActivities($q, $http, $scope));

is passing the result of promiseGetRecentActivities($q, $http, $scope) to then, so it must run before then is run, and so certainly doesn't wait for the promise from promiseGetWorkTypes to be resolved. What you seem to want is to pass it a function that, when called, runs promiseGetRecentActivities($q, $http, $scope), which is what

promiseGetWorkTypes($q, $http, $scope).then(function() {
  promiseGetRecentActivities($q, $http, $scope);
});

does.

As a side-note, it seems a bit unusual/overcomplicated to be passing $q, $http etc around to various functions, but I think probably beyond the scope of this question to go through alternatives.

Spender answered 8/2, 2015 at 9:10 Comment(8)
Wow! That seems to have worked, but I can't figure out why. I would have thought that the ".then" call would hold off on calling whatever function variable was passed in until the previous promise was resolved. And if it calls the "promiseGetRecentActivities" right away, why wouldn't it call the anonymous function right away? Is there some kind of logic that looks for a parameterless or unnamed method in the call to ".then"?Booze
@Booze I've added more to my answer to try to clarifySpender
That all makes sense and I should have realized I was doing that. I guess I got lost in thinking of functions as first-class objects and failed to notice I was calling the function and passing the result just like would be the case in any other language. THANK YOU for clarifying that. I have only one question left: Why does ".then" allow anything other than a function reference? What would it do with any other piece of data? I'm surprised I didn't encounter a run-time error from trying to pass the result of my call to "promseGetRecentActivities" instead of a function reference.Booze
@Booze I think that the angular $q implementation tries to be Promises/A+ compliant which states If onFulfilled is not a function, it must be ignoredSpender
Thank you! I really appreciate the answers and insight!Booze
@Booze No problem, and if you feel this answer answers your question, feel free to mark it as correct :-)Spender
This definitely answers my questions. Is there a way I can mark it correct with such a low (11) reputation? I'm trying to upvote your original answer and it tells me I need a 15 reputation to do that.Booze
@Booze There should be a tick symbol near the upvote/downvote buttons. If you press it, it should go green to indicate it's marked as correct (you can always press again to undo)Spender

© 2022 - 2024 — McMap. All rights reserved.