AngularJS : How to flatten this Promise chain? - javascript

I have the following code:
someService.fnReturnsPromise()
.then(function () {
return someService.fnReturnsAnotherPromise(someArg);
})
.then(function (resultsOfSecondFn) {
// do stuff with results
});
I feel as if this should work; however, resultsOfSecondFn isn't actually the results, it's the promise itself that I returned. To make it work the way I want, I have to do this:
someService.fnReturnsPromise()
.then(function () {
return someService.fnReturnsAnotherPromise(someArg);
})
.then(function (promiseReturn) {
promiseReturn.then(function (results) {
// do stuff with results
});
});
This is the pseudo-code for fnReturnsAnotherPromise:
someService.fnReturnsAnotherPromise = function (arg1) {
return anotherService.anotherFnThatReturnsPromise(arg1);
};
So really, it's just one extra layer, but a promise is getting returned either way. The code for anotherFnThatReturnsPromise is the simple paradigm of $q.defer(), return dfd.promise with some resolve()s.

Promises like Angular's are Promises/A+ compliant and are guaranteed to recursively assimilate promises. This is exactly to avoid nesting and simplify things like your case and is the point of promises.
So even if you have a promise that returns and a promise that returns a promise you can unwrap it in a single .then call. For example:
var p = $q.when(1); // Promise<Int>
var p2 = $q.when().then(function(){ return p; }); // Promise<Promise<Int>>
var p3 = $q.when().then(function(){ return p2; }); // Promise<Promise<Promise<Int>>>>
p3.then(function(result) {
console.log(result); // Logs 1, and Int and not p2's type
});
Or in your example:
someService.fnReturnsPromise()
.then(function() {
return someService.fnReturnsAnotherPromise(someArg);
})
.then(function(resultsOfSecondFn) {
// do work with results, it is already unwrapped
});
See this comparison with another language for perspective on promises not unwrapping.

you could try it this way:
someService.fnReturnsPromise().then(function () {
someService.fnReturnsAnotherPromise(someArg).then(function (results) {
console.log(results);
});
});
Hope it helps!

Related

Adding a Promise to Promise.all() [duplicate]

This question already has answers here:
How to know when all Promises are Resolved in a dynamic "iterable" parameter?
(5 answers)
Closed 6 years ago.
I've got an api call that sometimes returns paged responses. I'd like to automatically add these to my promises so I get the callback once all the data has arrived.
This is my attempt. I'd expect the new promise to be added and Promise.all to resolve once that is done.
What actually happens is that Promise.all doesn't wait for the second request. My guess is that Promise.all attaches "listeners" when it's called.
Is there a way to "reintialize" Promise.all()?
function testCase (urls, callback) {
var promises = [];
$.each(urls, function (k, v) {
promises.push(new Promise(function(resolve, reject) {
$.get(v, function(response) {
if (response.meta && response.meta.next) {
promises.push(new Promise(function (resolve, reject) {
$.get(v + '&offset=' + response.meta.next, function (response) {
resolve(response);
});
}));
}
resolve(response);
}).fail(function(e) {reject(e)});
}));
});
Promise.all(promises).then(function (data) {
var response = {resource: []};
$.each(data, function (i, v) {
response.resource = response.resource.concat(v.resource);
});
callback(response);
}).catch(function (e) {
console.log(e);
});
}
Desired flow is something like:
Create a set of promises.
Some of the promises spawn more promises.
Once all the initial promises and spawned promises resolve, call the callback.
It looks like the overall goal is:
For each entry in urls, call $.get and wait for it to complete.
If it returns just a response without "next", keep that one response
If it returns a response with a "next," we want to request the "next" as well and then keep both of them.
Call the callback with response when all of the work is done.
I would change #2 so you just return the promise and fulfill it with response.
A key thing about promises is that then returns a new promise, which will be resolved based on what you return: if you return a non-thenable value, the promise is fulfilled with that value; if you return a thenable, the promise is resolved to the thenable you return. That means that if you have a source of promises ($.get, in this case), you almost never need to use new Promise; just use the promises you create with then. (And catch.)
(If the term "thenable" isn't familiar, or you're not clear on the distinction between "fulfill" and "resolve", I go into promise terminology in this post on my blog.)
See comments:
function testCase(urls) {
// Return a promise that will be settled when the various `$.get` calls are
// done.
return Promise.all(urls.map(function(url) {
// Return a promise for this `$.get`.
return $.get(url)
.then(function(response) {
if (response.meta && response.meta.next) {
// This `$.get` has a "next", so return a promise waiting
// for the "next" which we ultimately fulfill (via `return`)
// with an array with both the original response and the
// "next". Note that by returning a thenable, we resolve the
// promise created by `then` to the thenable we return.
return $.get(url + "&offset=" + response.meta.next)
.then(function(nextResponse) {
return [response, nextResponse];
});
} else {
// This `$.get` didn't have a "next", so resolve this promise
// directly (via `return`) with an array (to be consistent
// with the above) with just the one response in it. Since
// what we're returning isn't thenable, the promise `then`
// returns is resolved with it.
return [response];
}
});
})).then(function(responses) {
// `responses` is now an array of arrays, where some of those will be one
// entry long, and others will be two (original response and next).
// Flatten it, and return it, which will settle he overall promise with
// the flattened array.
var flat = [];
responses.forEach(function(responseArray) {
// Push all promises from `responseArray` into `flat`.
flat.push.apply(flat, responseArray);
});
return flat;
});
}
Note how we never use catch there; we defer error handling to the caller.
Usage:
testCase(["url1", "url2", "etc."])
.then(function(responses) {
// Use `responses` here
})
.catch(function(error) {
// Handle error here
});
The testCase function looks really long, but that's just because of the comments. Here it is without them:
function testCase(urls) {
return Promise.all(urls.map(function(url) {
return $.get(url)
.then(function(response) {
if (response.meta && response.meta.next) {
return $.get(url + "&offset=" + response.meta.next)
.then(function(nextResponse) {
return [response, nextResponse];
});
} else {
return [response];
}
});
})).then(function(responses) {
var flat = [];
responses.forEach(function(responseArray) {
flat.push.apply(flat, responseArray);
});
return flat;
});
}
...and it'd be even more concise if we were using ES2015's arrow functions. :-)
In a comment you've asked:
Could this handle if there was a next next? Like a page 3 of results?
We can do that by encapsulating that logic into a function we use instead of $.get, which we can use recursively:
function getToEnd(url, target, offset) {
// If we don't have a target array to fill in yet, create it
if (!target) {
target = [];
}
return $.get(url + (offset ? "&offset=" + offset : ""))
.then(function(response) {
target.push(response);
if (response.meta && response.meta.next) {
// Keep going, recursively
return getToEnd(url, target, response.meta.next);
} else {
// Done, return the target
return target;
}
});
}
Then our main testCase is simpler:
function testCase(urls) {
return Promise.all(urls.map(function(url) {
return getToEnd(url);
})).then(function(responses) {
var flat = [];
responses.forEach(function(responseArray) {
flat.push.apply(flat, responseArray);
});
return flat;
});
}
Assuming you are using jQuery v3+ you can use the promises returned by $.ajax to pass to Promise.all().
What you are missing is returning the second request as a promise instead of trying to push it to the promises array
Simplified example
var promises = urls.map(function(url) {
// return promise returned by `$.ajax`
return $.get(url).then(function(response) {
if (response.meta) {
// return a new promise
return $.get('special-data.json').then(function(innerResponse) {
// return innerResponse to resolve promise chain
return innerResponse;
});
} else {
// or resolve with first response
return response;
}
});
})
Promise.all(promises).then(function(data) {
console.dir(data)
}).catch(function(e) {
console.log(e);
});
DEMO

AngularJS chain promises sequentially within for loop

How do i chain promises sequentially within for loop, i have seen lot of examples on google to do this but i couldn't implement for my case:
i have gone through this link for sequential chaining of Promises.
What I'm trying to acheive:
Promise1: login();
Promise2: sync();
sync function calls another service complete() for an array of elements. These array of elements must be done sequentially.
ServiceA.login().
then(function(response){
ServiceA.sync()
.then(function(response){
})
})
function sync(){
ServiceB.complete()
.then(function(){
var promises = [];
angular.forEach(response, function (value) {
// The below service call doSomething() must be done sequentially for each "value"
promises.push(doSomething(value));
});
$q.all(promises).then(function () {
});
});
})
}
How do I capture the error occuring in each Promise?
Update:
I have tried the approach suggested by #zaptree with the following code:
ServiceA.login()
.then(function(response){
// you must always return your promise
return ServiceA.sync()
})
// don't nest the .then make them flat like this
.then(function(response){
})
.catch(function(){
// if you made sure to always return your promises this catch will catch any errors throws in your promise chain including errors thrown by doSomething()
});
function sync(){
// you must always return your promise
return ServiceB.complete()
.then(function(){
var result = $q.when();
angular.forEach(response, function (value) {
result = result.then(doSomething(value)); // problem is here that doSomething function is being called before the first call it is resolved
// doSomething is a http call.
});
return result;
})
.then(function(){
// the array of promises has run sequentially and is completed
});
}
function doSomething(data){
return $http({
method: 'POST',
url: '/api/do',
data: data,
headers: {
"Content-Type": "application/json"
}
}).then(function (response) {
}, function (error) {
});
}
If the response in the near the for each loop has 2 values (valuea, valueb) in it, the code is behaving as follows:
1. calling doSomething(valuea)
2. calling doSomething(valueb) before the above promise is resolved.
Expected behaviour:
after the POST method has succesfully completed by the call doSOmething(valuea), then the another POST call should happend i.e., soSomething(valueb).
Here's what I came up with. You'll need to reduce the array into a single promise.
var results = [...];
var sequentialPromise = results.reduce(function(a, b) {
return a.then(function(){
return doSomething(b);
});
}, $q.resolve());
sequentialPromise.then(function(){...});
So here is an example on how you would do the sequential promises with Q, also some improvements on how to do your promises so you can properly catch errors thrown at any point in your promise chain. You must always make sure to return a promise on any method that uses them. Also avoid pyramid code by not nesting the .then to make your code cleaner:
ServiceA.login()
.then(function(response){
// you must always return your promise
return ServiceA.sync()
})
// don't nest the .then make them flat like this
.then(function(response){
})
.catch(function(){
// if you made sure to always return your promises this catch will catch any errors throws in your promise chain including errors thrown by doSomething()
});
function sync(){
// you must always return your promise
return ServiceB.complete()
.then(function(){
var result = $q.when();
angular.forEach(response, function (value) {
result = result.then(doSomething(value));
});
return result;
})
.then(function(){
// the array of promises has run sequentially and is completed
});
}

Why isn't .then() waiting for the promise?

I am currently working on a angular project, and I am kind of new to it.
I do not understand, why is .then() function not waiting for the promises?
I think it have to do something with that I only have one $q.defer() inside my getAllStats() function? When I try to console.log("testing: ", data); (on the bottom) it only logs out an empty array. Could someone help me please?
This is my code:
function getAllStats(dataArray, nameOfFile) {
var defer = $q.defer();
var promises = [];
for (index in dataArray) {
if (dataArray[index].indexOf('test') > -1 ) {
getStats(nameOfFile).then(function (data) {
promises.push();
});
}
}
function last() {
defer.resolve(promises);
}
$q.all(promises).then(last);
return defer.promise;
};
function getStats(nameOfFile) {
var defer = $q.defer();
$http.get(nameOfFile).success(function (data) {
defer.resolve(data);
});
return defer.promise;
};
getAllStats('test.txt').then(function(data) {
console.log("testing: ", data);
});
See the comments in this code:
function getAllStats(dataArray, nameOfFile) {
var promises = [];
// using `for (index in dataArray) {` is a bad idea unless
// dataArray is a non-array object
for (var index = 0; index < dataArray.length; index++) {
if (dataArray[index].indexOf('test') > -1 ) {
// Here is the trick, store the promise itself,
// don't try to subscribe to it here
promises.push(getStats(nameOfFile));
}
}
return $q.all(promises);
};
function getStats(nameOfFile) {
// http.get already returns a promise, see explicit promise creation antipattern
return $http.get(nameOfFile).then(function(r) { return r.data; });
};
getAllStats('test.txt').then(function(data) {
console.log("testing: ", data);
});
References:
Explicit promise creation antipattern
Why is for..in bad
Deprecation Notice
The $http legacy promise methods success and error
have been deprecated. Use the standard then method instead. If
$httpProvider.useLegacyPromiseExtensions is set to false then these
methods will throw $http/legacy error.
see: $http
In your example this block:
for (index in dataArray) {
if (dataArray[index].indexOf('test') > -1 ) {
getStats(nameOfFile).then(function (data) {
promises.push();
});
}
}
Takes dataArray, which is a string and runs through it char by char. Also, you are not setting nameOfFile. Change the call to:
getAllStats(['test.txt']).then(function(data) {
console.log("testing: ", data);
});
And then to make the push to promises be correct do like this:
promises.push(getStats(dataArray[index]));
Multiple issues wrong here:
You're passing an empty promises array to $q.all(). It has to be an array of promises at the time you pass it to $q.all().
You're creating promises when you can just return the ones you have
getAllStats() expects an array, but you're passing a string.
I'd suggest this overall cleanup of the code that fixes the above issues:
function getAllStats(dataArray) {
var promises = dataArray.filter(function(item) {
return item.indexOf('test') !== -1;
}).map(function(item) {
return $http.get(item);
});
return $q.all(promises);
};
getAllStats(['test.txt']).then(function(data) {
console.log("testing: ", data);
});
I'd also suggest you read about promise anti-patterns to teach yourself how to use the promises you already have and avoid creating new ones when new ones are not necessary.
P.S. I'm not sure what was the point of the nameOfFile argument since you don't want to be getStats() on the same file over and over again.

Managing promise dependencies

I'm using Node.js and Bluebird to create some fairly complicated logic involving uncompressing a structured file, parsing JSON, creating and making changes to several MongoDB documents, and writing related files in multiple locations. I also have fairly complicated error handling for all of this depending on the state of the system when an error occurs.
I am having difficulty thinking of a good way to manage dependencies through the flow of promises.
My existing code basically looks like this:
var doStuff = function () {
var dependency1 = null;
var dependency2 = null;
promise1()
.then(function (value) {
dependency1 = value;
return promise2()
.then(function (value) {
dependency2 = value;
return promise3(dependency1)
.then(successFunction);
});
})
.catch(function (err) {
cleanupDependingOnSystemState(err, dependency1, dependency2);
});
};
Note that dependency1 isn't needed until promise3, and that the error handler needs to know about the dependencies.
To me this seems like spaghetti code (and my actual code is far worse with a lot of parallel control flow). I've also read that returning another promise inside of a .then callback is an antipattern. Is there a better/cleaner way of accomplishing what I'm trying to do?
I find both answers currently provided nice but clumsy. They're both good but contain overhead I don't think you need to have. If you instead use promises as proxies you get a lot of things for free.
var doStuff = function () {
var p1 = promise1();
var p2 = p1.then(promise2);
var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do.
return Promise.all([p1, p2, p3]).catch(function(err){
// clean up based on err and state, can unwrap promises here
});
};
Please do not use successFunction and such it is an anti-pattern and loses information.
If you feel like you have to use successFunction you can write:
var doStuff = function () {
var p1 = promise1();
var p2 = p1.then(promise2);
var p3 = p1.then(promise3); // if you actually need to wait for p2 here, do.
Promise.join(p1, p2, p3, successFunction).catch(function(err){
// clean up based on err and state, can unwrap promises here
});
};
However, it is infinitely worse since it won't let the consumer handle errors they may be able to handle.
This question might be more appropriate for code review but here is how I'd approach it given this example:
var doStuff = function () {
// Set up your promises based on their dependencies. In your example
// promise2 does not use dependency1 so I left them unrelated.
var dep1Promise = promise1();
var dep2Promise = promise2();
var dep3Promise = dependency1Promise.then(function(value){
return promise3(value);
});
// Wait for all the promises the either succeed or error.
allResolved([dep1Promise, dep2Promise, dep3Promise])
.spread(function(dep1, dep2, dep3){
var err = dep1.error || dep2.error || dep3.error;
if (err){
// If any errored, call the function you prescribed
cleanupDependingOnSystemState(err, dep1.value, dep2.value);
} else {
// Call the success handler.
successFunction(dep3.value);
}
};
// Promise.all by default just fails on the first error, but since
// you want to pass any partial results to cleanupDependingOnSystemState,
// I added this helper.
function allResolved(promises){
return Promise.all(promises.map(function(promise){
return promise.then(function(value){
return {value: value};
}, function(err){
return {error: err};
});
});
}
The use of allResolved is only because of your callback specifics, if you had a more general error handler, you could simply resolve using Promise.all directly, or even:
var doStuff = function () {
// Set up your promises based on their dependencies. In your example
// promise2 does not use dependency1 so I left them unrelated.
var dep1Promise = promise1();
var dep2Promise = promise2();
var dep3Promise = dependency1Promise.then(function(value){
return promise3(value);
});
dep3Promise.then(successFunction, cleanupDependingOnSystemState);
};
It is certainly not an antipattern to return promises within thens, flattening nested promises is a feature of the promise spec.
Here's a possible rewrite, though I'm not sure it's cleaner:
var doStuff = function () {
promise1()
.then(function (value1) {
return promise2()
.then(function (value2) {
return promise3(value1)
.then(successFunction)
.finally(function() {
cleanup(null, value1, value2);
});
})
.finally(function() {
cleanup(null, value1, null);
});
})
.finally(function () {
cleanup(null, null, null);
});
};
Or another option, with atomic cleanup functions:
var doStuff = function () {
promise1()
.then(function (value1) {
return promise2()
.then(function (value2) {
return promise3(value1)
.then(successFunction)
.finally(function() {
cleanup3(value2);
});
})
.finally(function() {
cleanup2(value1);
});
})
.finally(function (err) {
cleanup1(err);
});
};
Really, I feel like there's not much you can do to clean this up. Event with vanilla try/catches, the best possible pattern is pretty similar to these.

node q promise recursion

I have a async function that returns a random student. Now I want a function that returns two unique students- the source of my problems.
getTwoRandom = function(req) {
var deferred = Q.defer();
Q.all([
Student.getRandom(req),
Student.getRandom(req)
])
.then(function(students){
if(students[0]._id !== students[1]._id) { //check unique
deferred.resolve(students);
} else {
//students are the same so try again... this breaks
return getTwoRandom(req);
}
});
return deferred.promise;
};
then further down I have something like this:
getTwoRandom(req).then(function(students) {
//do what I want...
});
The problem is when I do return getTwoRandom(req); the .then() function down the lines doesnt fire... is this returning a different promise that .then() isnt using?
You've over-complicated it quite a bit :)
You can do it like this instead:
getTwoRandom = function(req) {
return Q.all([
Student.getRandom(req),
Student.getRandom(req)
]).then(function(students) {
if(students[0]._id !== students[1]._id) {
return students;
} else {
return getTwoRandom(req);
}
});
};
Now, why does this work? The result of Q.all is always a new promise (no need to create a new deferred). Whatever value you return (ike students) will be wrapped in this new promise. If instead an actual promise is returned (like getTwoRandom(req)), then that promise will be returned. Which sounds like what you wanted to do.

Categories