I am struggling with chaining promises using $timeouts. I would like to have a "$timeout(myFunction,1000).then()" function that fires only when ALL chained timeouts returned by myFunction are resolved.
This code snippet contains different stuff I tried and I would like to achieve:
$timeout(myFunction,1000).then(function(myPromise) {
console.log("I would like this message to appear when ALL chained promises are resolved, without knowing in advance how many chained promises there are. In this case this would be after 1500 ms, not 1000ms")
myPromise.then(function()) {
console.log("with this code patern I get a message after 1500ms, which is what I want, but this does not work anymore if myOtherFunction would return a third chained $timeout")
}
})
myFunction = function() {
console.log("hi, i am launching another timeout")
return $timeout(myOtherFunction, 500)
}
myOtherFunction = function () {
console.log("1500 ms have passed")
}
How should I fix my code? Thanks!
Return promises to the success handler:
$timeout(null,1000).then(function() {
console.log("It is 1000ms");
var delay = 500
return myPromise(delay);
// ^^^^^^ return promise for chaining
}).then(function() {
console.log("this happens after myPromise resolves");
});
function myPromise(delay) {
promise = $timeout(null, delay);
return promise;
});
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.
-- AngularJS $q Service API Reference -- Chaining promises;
Inspired by the answer of georgeawg I created my custom timeout function that returns the promise returned by fct, instead of the promise returned by $timeout. I did this to keep the $timeout syntax.
vm.customTimeout = function (fct, timeout){
return $timeout(fct, timeout).then(function(myReturnedPromise){
return myReturnedPromise
});
}
This function is sufficient to solve my problem above. I can chain as much customTimeouts I want.
Example :
vm.customTimeout(myFunction,1000).then(function() {
var activity1 = anyFunctionReturningAPromise(100);
var activity2 = anyFunctionReturningAPromise(1000);
return $q.all([activity1, activity2])
console.log("Without knowing the content of myFunction, I am 100% sure that
every single chained promise retuned by myFunction is resolved before
executing this code, which is quite nice!")
}).then(function(){
console.log("executes when customTimeout, activity1 & activity2 are all resolved.")
})
anyFunctionReturningAPromise = function(delay) {
return vm.customTimeout(myFunction, delay)
}
Feel free to comment what you think of it.
I hope this will be useful for someone else :)
Related
I have a following piece of code:
function getData(foos, bars) {
var generated = {};
return $q(function(resolve, reject) {
var promises = _.map(foos, function(foo) {
return _.map(bars, function(bar) {
return someServiceCall(foo, bar).then(function(data) {
_.set(generated[foo.id], player.id.toString() + '.data', data);
});
});
});
// Join all promises in to a final resolve
$q.all(promises).then(function() {
resolve(generated);
}, reject);
});
}
What I want to achieve is to have all the someServiceCall-s and it's success handlers finished by the time resolve(generated) is called. When I debug this part of the code in the developer toolbar, resolve(generated) is called before the success handler of someServiceCall is called for every promise.
Note: This not always breaks the functionality, as the objects are passed as a reference, so the data is set even if resolve was already called, but I think the functionality would be clearer if all those calls were finished by the time resolve is called.
I just realized my dumb mistake. The two nested maps resulted the following structure:
var promises = [[Promise, Promise][Promise, Promise, Promise]];
While $q.all expects an array of promises:
var promises = [Promise, Promise, Promise, Promise, Promise];
I could easily solve this by replacing the first map call by flatMap:
return _.flatMap(bars, function(bar) {
It's still strange for me that $q.all silently just resolved the promise without an error or warning that the data format is not appropriate.
I hope I can help someone who runs into this problem in the future.
Im using angularjs 1.4 and jasmine 2.4.
Im trying to test a function and I want to make it return a promise, so another layer above it can deal possible values.
My issue is that the function first validate the inputs. If they are not the right ones I want to return a rejected promise. Otherwise it will do whatever it has to do and resolve the promise.
Here is part of the function from the emailSvc in question:
// Function found in the emailSvc
this.sendEmail = function sendEmail(apiKey, token, email_type)
{
// Prerequisite to send email
if(!apiKey) {
return $q.when("apiKey not present.");
}
var deferred = $q.defer();
// Ajax call
serviceApiEmail.send(apiKey, token, email_type)
.then(function(data){
deferred.resolve(data);
})
.catch(function(e){
deferred.reject(e);
})
return deferred.promise;
}
And my test case is like follow:
it('should reject sending email if apiKey is not present', function(){
var rejectEmail;
var apiKey,
verifyToken = acceptedVerifyToken,
emailType = const_EMAIL_TYPE.SIGNUP;
i_emailSvc.sendEmail(apiKey, verifyToken, emailType)
.then(function(){
// It should not come here
rejectEmail = false;
}).catch(function(){
rejectEmail = true;
});
// It comes here without executing any success or fail promise handlers
expect(rejectEmail).toBe(true);
});
The issue is that when rejecting the promise the catch is never executed. I believe this is with a misconception I have with promises.
Any ideas why the catch and then are not working in here?
You are resolving the promise if the apiKey is not present when using $q.when(). This would indicate a successfully completed promise. To indicate failure you should use $q.reject i.e.
if(!apiKey) {
return $q.reject("apiKey not present.");
}
Additionally for the then or catch callbacks to be executed in your test you would usually need to trigger a digest cycle. The usual way of doing this is tests is to get a reference to the $rootScope and call $rootScope.$digest().
Are you mocking the serviceApiEmail.send method? Are you reaching the then at all? I'd check for something else then rejectEmail boolean because with Javascript your test is going to be false even if the 'then' part is not reached, so you aren't maybe even reaching the promise resolve part
I have read every article on $q out there but somehow I am unable to grasp it. For example take the best article I found. I get as far as:
var myFirstDeferred = $q.defer(); //Actually I don't even get this far
A deferred represents the result of an asynchronic operation. It exposes an interface that can be used for signaling the state and the result of the operation it represents. It also provides a way to get the associated promise instance.
What's a 'deferred'? How is it different from a promise? Then we get to this:
async(function(value) {
myFirstDeferred.resolve(value);
}, function(errorReason) {
myFirstDeferred.reject(errorReason);
});
I have NO idea what this is doing. I want to stress that I understand async. I understand the promise structure. For example I know exactly what the code below is doing:
$http.get(url)
.then(function(result){returnresult}
,function(error){return error})
But what are we doing with the deferred in the object above? Why even have the deferred at all? Why not just the then block?
Edit: I want to stress that I went through a ton of replies here, as well as the articles. I thought the point of $q was the force the execution of an async call (kind of like "await" in c#) and then perform some code after. I really don't understand how it does that. I do get how the .all command works when waiting for multiple async operations in this example, but not with one.
Edit: This edit is in response to the duplicate suggestion - I disagree with the notion. For one, this question is more focused and limited in scope. In addition, the answer accepted here clarifies far better (imo) than the wide-net answer in the other q.
Typically you don't have to use a deferred explicitly. If you find yourself using $q.defer you should question why you are not using the promise interface directly. See the bluebird documentation for a description of why. $http and many other libraries use deferreds internally and return promises from the methods they expose so using deferreds on top of this is unnecessary.
That being said, deferreds can be useful and understanding them is important. Typically, deferreds have two methods: .reject and .resolve. Honestly there could be one method that you could use to mark the result of an asynchronous operation:
.reject -> the asynchronous operation failed or could not complete
.resolve -> the asynchronous operation completed successfully
When a deferred is completed, it triggers the promise callbacks.
You need to use $q.defer or deferreds when dealing with operations that are asynchronous but do not have a built in promise interface such as timers:
var dfd = $q.defer();
$timeout(function () {
// this is asynchronous
// it completed successfully
dfd.resolve("some value");
}, 500);
dfd.promise.then(function (value) {
assert.equal(value, "some value");
});
Rather than using deferreds as an object interface, Angular allows you to use these destructured as function arguments to $q
Rewriting the above:
var promise = $q(function (resolve) {
$timeout(function () {
resolve("some value");
}, 500);
});
promise.then(function (value) {
assert.equal(value, "some value");
});
A deferred object is just a subset of a promise that only gives you .reject and .resolve, which prevents the outside world from doing anyting else with it. If you can't return a promise (like when using a library built on callbacks, not promises), make a deferred and manually reject or resolve it.
deferred.reject and deferred.resolve only matter when you return the deferred. Basically:
somePromise().then( function() {
var deferred = q.defered();
someFunctionThatHasCallback( function() {
deferred.resolve();
});
return defererd;
}).then( function() {
console.log( 'I wait for deferred.resolve' );
});
If your functions return promises, you don't need deferred, because promises are chainable by returning inside .then
somePromise().then( function() {
return someFunctionThatReturnsPromise();
}).then( function() {
console.log( 'I wait for promise to resolve' );
});
For an angular project, I have to nest promises and I run into cases where I am not sure of what I am doing.
Here is one of my code :
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
$q.all([Action2(), Action3(), Action4()]).then(function () {
defer.resolve();
});
} else {
defer.reject("error_code");
}
return defer.promise;
});
Action1, Action2, Action3 and Action4 are working promises functions. It's a lot of promises and actions depend on conditions.
Can I do that and be sure my main function will be always resolved or rejected?
I read that we can pass promise inside resolve function.
Can I do that and is this the same as above:
return Action1().then(function (data) {
var defer = $q.defer();
if (data.condition) {
defer.resolve($q.all([Action2(), Action3(), Action4()]);
} else {
defer.reject("error_code");
}
return defer.promise;
});
No, it is not. Your first function would stay forever pending if one of Action2(), Action3() or Action4() did "throw", and reject the $q.all(…) promise - your deferred is never resolved then. This is the most common bug of the deferred antipattern you've used here.
Your second function does mitigate this, but is still unncessary complicated. You don't need a deferred here at all! Just return the promise directly, and use $q.reject:
return Action1().then(function (data) {
if (data.condition) {
return $q.all([Action2(), Action3(), Action4()]);
} else {
return $q.reject("error_code");
}
});
Or, as this happens inside a then handler, you can also use throw "error_code".
Thanks for your answer, I can see my error on the first code version. I think it's the q.all which perturbs me.
I read the deferred antipattern. It said that we don't have to create deferred objects for no reason.
The simple case is this :
return Action1().then(function () {
return $q.all([Action2(),Action3(), Action4()]);
});
But due to the if (data.condition) I can't do it.
Is my second code the only way to do it? Am I in a case or I have to use defer?
It speaks about "promisification", but with Angular I don't know if it's a good thing (libs seem unmaintained).
Cheers,
I think you find this question thousands of times...but I can't really understand the way to solve.
I have a $http request inside a Service...
app.service('getData', function ($http) {
this.getDataList = function () {
$http.get('../content/catalog/data.json')
.success(function(response) {
return response;
})
};
I call it from the app.run
app.run(function (getData) {
list=getData.getDataList()
})
If I log the list variable is undefined
What is the way to sync them?
Thank you for the help!!!!
You're treating the call as if it were synchronous, where as its an Async call, so when you say return response; that line executes when the call hasn't finished yet and naturally you get undefined, use a callback instead or return a promise:
this.getDataList = function (callback) {
$http.get('../content/catalog/data.json')
.success(function(response) {
callback(response);
})
};
Usage:
getData.getDataList(function(data){
var list=data;
console.log(data);
});
EDIT:
Regarding promises, the idea is very simple, whenever you have an async operation that you expect will not be completed immediately, i.e. ajax calls, you can use a promise returned by the method making that async operation in order to find out when the task has finished.
For example $http returns a promise by default, so you can change your code to make use of that promise by simply returning the $http call itself:
this.getDataList = function () {
return $http.get('../content/catalog/data.json');
}
and then use it like this:
getDataList().then(function(successData){
var list=successData;
},function(errorResponse){
alert("something terrible has happened!");
})
The promise returned by $http takes 2 callbacks, the first for a successful call and the second is for errors.
Nowadays I mostly just pass callbacks in to the function making the async call, saves me from having to write then().
list is undefined because you forgot to return $http.get in this.getDataList()
By the way, $http will return a promise (an object with then and finally methods), which you can directly use in ng-bind for instance.
Use promises instead.
JavaScript uses promises for async(deferred) operations. These promises are based on callbacks(I promise to run your callback when I'm done).
getDataList does a http.get behind the scenes and doesn't block the code. It simply returns a promise object.
You can add callbacks to the promise object that will happen when the async operation finishes.
app.service('getData', function ($http) {
this.getDataList = function () {
return $http.get('../content/catalog/data.json');
};
});
app.run(function (getData) {
getData.getDataList().then(function(res){
list = res
});
});