Promise to run nested promises sequentially and resolve upon first reject - javascript

I'm struggling to send multiple AJAX calls in the following task. The API returns takes two parameters: userId and offsetValue and returns last 10 messages for the specified user, starting from specified offset. If the offset is more than the total count of messages for the user, API returns an empty string.
I wrote a function that returns an individual promise to get 10 messages for specified userId and offsetValue.
function getMessages(userId, offsetValue) {
return new Promise(function (resolve, reject) {
$.ajax(
{
url: 'https://example.com/api.php',
type: 'POST',
data: {
action: 'get_messages',
offset: offsetValue,
user: userId
},
success: function (response) {
if (response != '') {
resolve(response);
} else {
reject(response);
}
},
error: function (response) {
reject(response);
}
});
});
}
I need to run parallel tasks using .all() for multiple userId, however I cannot run parallel subtasks for each userId (incrementing offsetValue by 10 each time) as I don't know in advance how many messages does each user have, so the execution should stop when first individual promise is rejected (i.e. offsetValue is more than total messages count). Something like this:
var messages = '';
getMessages('Alex', 0)
.then(function(result) {
messages += result;
getMessages('Alex', 10);
.then(function(result) {
messages += result;
getMessages('Alex', 20)
....
});
});
So, is there any way to run sequental promises with iterating parameter sequentially one by one and resolve the overall concatenated result upon first reject?

First off, you want to avoid the promise anti-pattern where you unnecessarily wrap your code in a new promise when $.ajax() already returns a promise that you can just use. To fix that, you would change to this:
// retrieves block of messages starting with offsetValue
// resolved response will be empty if there are no more messages
function getMessages(userId, offsetValue) {
return $.ajax({
url: 'https://example.com/api.php',
type: 'POST',
data: {
action: 'get_messages',
offset: offsetValue,
user: userId
}
});
}
Now, for your main question. Given that you want to stop requesting new items when you get a rejection or empty response and you don't know how many requests there will be in advance, you pretty much have to request things serially and stop requesting the next one when you get an empty response or an error. The key to doing that is to chain sequential promises together by returning a new promise from a .then() handler.
You can do that like this:
function getAllMessagesForUser(userId) {
var offsetValue = 0;
var results = [];
function next() {
return getMessages(userId, offsetValue).then(function(response) {
// if response not empty, continue getting more messages
if (response !== '') {
// assumes API is returning 10 results at a time
offsetValue += 10;
results.push(response);
// chain next request promise onto prior promise
return next();
} else {
// empty response means we're done retrieving messages
// so just return results accumulated so far
return results.join("");
}
});
}
return next();
}
This creates an internal function that returns a promise and each time it gets some messages, it chains a new promise onto the original promise. So, getAllMessagesForUser() returns a single promise that resolves with all the messages it has retrieved or rejects with an error.
You would use it like this:
getAllMessagesForUser('Bob').then(function(messages) {
// got all messages here
}, function(err) {
// error here
});
You could parallelize multiple users (as long as you're sure you're not overloading the server or running into a rate limiting issue) like this:
$.when.apply($, ['Bob', 'Alice', 'Ted'].map(function(item) {
return getAllMessagesForUser(item);
})).then(function() {
// get results into a normal array
var results = Array.prototype.slice.call(arguments);
});
P.S. Promise.all() is much nicer to use than $.when() (since it takes an array and resolves to an array), but since there are already jQuery promises involved here and I didn't know your browser compatibility requirements, I stuck with jQuery promise management rather than ES6 standard promises.

Related

Node JS, chaining variable number of http requests

I'm using request js library to make HTTP requests to my API. My single API call looks like this:
var options = {
method: "post",
url: 'http//example.com',
json: true,
headers: headers,
body: {key: value}
}
request(options, callback);
However, I have array of options, which are needed to be called one after another and I need to break whole chain if one of them fails.
If last chain finishes, I need to output result to console.
I know that chaining callbacks could be fulfilled via promises, but all examples that I have found uses predefined amount of chained requests.
Is it possible?
A recursive function which calls itself in the request callback should work.
options = [{...}, {...}];
function doRequests(options){
request(options.shift(), function(){
if(error){
return "handle error";
}
if(options.length > 0){
doRequests(options);
}
});
}
The first thing I would do would be to use a request library that returned a promise. Assuming you have such a thing then you just chain the promises together.
First create a resolved promise:
var promise = new Promise.resolve();
The for each new object you want to request:
promise = promise.then(() => requestToPromise(options));
will chain another request onto the existing promise and will fire off a new request only when the previous one has completed.
If you have an array, you can have an index into that array, and have the callback kick off the next request when the previous one finishes. Roughly:
var index = 0;
var options = [/*...array of options objects...*/];
doRequest() {
request(options[index], function(err, result) {
// ...handle the result/error here. If there's no error, then:
if (++index < options.length) {
// Kick off the next request
doRequest();
}
});
}
While the above can be Promise-ified, since your requestmethod appears not to be, it would just complicate things.
You can instead use request-promise
and do the following
import request = require('request-promise');
var options = [/*...array of options objects...*/];
requests = [];
options.forEach(function(option){
requests.push(request(option));
}
Promise.all(requests).then(function(reponses){
//all requests are done.
});

Using promises to request order-dependent information in a for loop

I have a relatively simple task; existing information is out of date, so I have to request information from an API, modify the existing info file and push it back to the server, updated. The information is in the form of a json file; simple enough. That file contains an object with an array of objects that have several properties that must be updated. This is where the problems occur; the array of objects generates an array of API requests, whose responses must match the original object that spawned the request (since the response contains info that must be updated in the object).
This is the gist of what I've managed to do so far with promises:
function main() {
// First get existing data.
getExistingData().then(function(result) {
console.log(result); // It worked, return it for next 'then' to use.
return result;
}, function(err) {
console.log(err); // This usually never happens.
}).then(function(result) { // Use the existing data to generate the requests for new data.
requestNewData(result).then(function(moddedJson) {
console.log(moddedJson); // This happens BEFORE I get responses back from the request, which is wrong.
});
});
}
function getExistingData() {
return new Promise(function(resolve, reject) {
fetch('dataURLHere')
.then(function(res) {
resolve( res.json()); // Turn result into JSON, and return it.
})
})
}
function requestNewData(rawJson) {
return new Promise(function(resolve) {
// Loop over the number of objects in the original data.
for (var i = 0; i < rawJson.length; i++) {
// Loop over the array of objects within each object.
for (var multiId = 0; multiId < rawJson.hits.length; multiId++) {
var requestUrl = "someURLConstructedFromJsonData";
var hit = rawJson.hits[multiId];
new Promise(function(resolve) {
request(requestUrl, function(error, response, body) {
if (!error && response.statusCode == 200) {
// Need to parse the XML response into a js object.
parseString(body, function (err, result) {
hit.propertyToChange = result.propertyToChange;
hit.propertyToChange2 = result.propertyToChange2;
});
}
else {
console.log("No data for this item.");
}
resolve(hit);
});
})
}
}
resolve(rawJson);
})
}
Basically, the things I want to happen are:
1) Get original data. This is easy and accomplished by my code already.
2) Use original data to generate requests for each document in the data, and for each set of properties within each document. This is also not a problem.
3) Ensure the returning data from requests gets matched to existing data. THIS is the problem part that I can't wrap my head around.
The problem is that you're resolving too early.
The red flag is when you create a promise but never do anything with it:
new Promise(function(resolve) {
request(requestUrl, function(error, response, body) {
...
That promise does get resolved correctly, but no one is waiting on it. The simple solution is Promise.all:
function requestNewData(rawJson) {
return new Promise(function(resolve, reject) {
var promises = [];
for (var i = 0; i < rawJson.length; i++) {
...
promises.push(new Promise(function(resolve) {
...
}));
}
resolve(Promise.all(promises));
});
}
Now, Promise.all(promises) will resolve with an array of results. This might not be ideal, but you can but a then on it if you just want to wait using it:
return Promise.all(promises).then(function() {
resolve(updatedJson);
}, reject);
In this way, you could have each of the individual promises modify the response data. The promise returned by requestNewData won't resolve until all of them are done, so at that point updatedJson would be updated.
Be warned: Promise.all has fast-fail behavior. In your case, I think this is exactly what you want. But if you need to know which ones failed, or if you need to wait until all requests complete (fail or otherwise), Promise.all may not be the right thing.
PS: You should maybe reject if the request() function provides an error. Otherwise you could have holes in your data if there's a network error, without actually getting a reject.

javascript api call, send and handle data in specific order

I have this API call where i make sure the data return in the same order i send it. However, i realized thats not really what i want, i want to make sure the data is send and taken care of one at a time.
data[n] has returned before data[n+1] is send.
the reason for this is:
If i do it as seen below, the server still gets it in a random order, and therefor saves the data in my DB in a random order. (or well not random, heavier data gets processed slower)
var promiseArray = [];
for (var i = 0; i < data.length; i++) {
var dataPromise = $http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then (function (response) {
//return data for chaining
return response.data;
});
promiseArray.push(dataPromise);
}
$q.all(promiseArray).then(function (dataArray) {
//succes
}).catch (function (errorResponse) {
//error
});
how can i make sure the data is send and processed and returned, one at a time in a smooth way ?
You could do something like this:
var i = -1;
processNextdata();
function processNextdata() {
i++;
if(angular.isUndefined(data[i]))
return;
$http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then(processNextdata)
}
Update:
Callback after every result:
var i = -1;
processNextdata();
function processNextdata() {
i++;
if(angular.isUndefined(data[i]))
return;
$http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then(function(result) {
// do something with a single result
return processNextdata();
}, errorCallback);
}
Callback after everything is done:
var i = -1, resultData = [];
processNextdata()
.then(function(result) {
console.log(result);
}, errorCallback);
function processNextdata() {
i++;
if(angular.isUndefined(data[i]))
return resultData;
$http.post('/api/bla/blabla', $httpParamSerializer(data[i]))
.then(function(result) {
resultData.push(result.data);
return processNextdata();
}, $q.reject);
}
When using the Promise.all([...]) method, the documentation shows the following:
The Promise.all(iterable) method returns a promise that resolves when all of the promises in the iterable argument have resolved, or rejects with the reason of the first passed promise that rejects.
What this tells us is that there is no expected order of synchronized operations, but in fact the promises run parallel to one another and can complete in any order.
In your case, there is an expected order that you want your promises to run in, so using Promise.all([...]) won't satisfy your requirements.
What you can do instead is execute individual promises, then if you have some that can run in parallel use the Promise.all([...]) method.
I would create a method that takes a request as an argument, then returns the generated promise:
function request (req) {
return new Promise(function (resolve, reject) {
request({
url: url
, port: <port>
, body: req
, json: <true/false>
, method: '<POST/GET>'
, headers: {
}
}, function (error, response, body) {
if (error) {
reject(error);
} else {
resolve(body);
}
});
});
You can then call this function and store the result:
var response = request(myRequest);
Alternatively, you could create an array of your requests and then call the function:
var requests = [request1, request2, ..., requestN];
var responses = [];
for (var i = 0; i < requests.length; i++) {
responses.push(request(requests[i]));
}

To Execute Ajax requests in Sequence

Thanks for viewing my question. I am hopeful that you might help me go in the right direction.
Problem:
User sends a list of company name and server sends the corresponding logo back.
The restriction is that the logo needs to be placed left to right and then top to bottom.
The order should not change.
Approach Followed:
We are using an Array of Company Names appearing row wise -
Like:
[ [Companies_Row1] , [Companies_Row2], [Companies_Row3] ]
Each [Companies_Row] has values like [‘Ford’, ‘BMW’, ‘Audi']
[
We expect the logos to appear in order, however presently we are getting them in random order.
Solution we are thinking is to iterate over the arrays and make use of Promises.
However we are struggling to implement this since last two days. Please help us resolve the same.
Code Snippets:
// arrayOfCompanyNames - contains an Array of [Array of Company Names] appearing in a row. Please Refer Screenshot.
// Function where we are traversing the company Name and making call
function getCompanyLogos(arrayOfCompanyNames) {
var promise = new Promise(function(resolve, reject) {
resolve(1);
});
promise.then(function(){
arrayOfCompanyNames.forEach(function(item) {
item.forEach(function(value, index) {
if(index == item.length - 1){
// When a row is complete then move to the next row.
promise = promise.then(LogoFetchPromis(value, true));
} else {
promise = promise.then(LogoFetchPromis(value));
}
});
});
});
}
function LogoFetchPromis(carName, wrapNow){
return new Promise(function(resolve, reject) {
$.ajax({
url: "/getLogo/" + carName,
dataType: 'text'
})
.done(function(response) {
resolve(response);
// logos is the section where logos should appear
$('.logos').append(response);
if (wrapNow)
$('.logos').append('<br>');
})
.fail(function(response) {
reject(Error(response));
});
});
}
Please help us find a resolution for the same.
Keep it simple. One solution is first fetch all of your images by building an array of promises reproducing the structure of arrayOfCompanyNames then use Promise.all() to keep the order of the promises result. And then you can loop over the results array and show the logos (add error management as you need).
// arrayOfCompanyNames - contains an Array of [Array of Company Names] appearing in a row. Please Refer Screenshot.
var arrayOfCompanyNames;
// Function where we are traversing the company Name and making call
function getCompanyLogos(arrayOfCompanyNames) {
// build an array of promises mirroring arrayOfCompanyNames
var promises = [];
arrayOfCompanyNames.forEach(function (subArray) {
var subPromises = [];
subArray.forEach(function (carName) {
// $.ajax is a promise so you use it directly
subPromises.push($.ajax({
url: '/getLogo/' + carName,
dataType: 'text'
}));
});
promises.push(Promise.all(subPromises));
});
// show everything, results is nicely formatted so you know when to <br>
return Promise.all(promises).then(function done(results) {
// results look like [[row1], [row2], [row3]]
results.forEach(function (row) {
row.forEach(function (carLogo) {
$('.logos').append(carLogo);
});
$('.logos').append('<br>');
});
})
}
The Promise.all() solution will introduce a delay, the logos will show at once when they're all fetched. They will be downloaded all at the same time so be careful if it's not your server you're requesting (almost 100 concurrent requests is consequent).
The other way is what you started, building a promise chain.
// arrayOfCompanyNames - contains an Array of [Array of Company Names] appearing in a row. Please Refer Screenshot.
var arrayOfCompanyNames;
// Function where we are traversing the company Name and making call
function getCompanyLogos(arrayOfCompanyNames) {
// this is enough to create a 'base' resolved promise
var promise = Promise.resolve();
arrayOfCompanyNames.forEach(function (item) {
item.forEach(function (value, index) {
if (index == item.length - 1) {
// When a row is complete then move to the next row.
promise = promise.then(LogoFetchPromis.bind(null, value, true));
} else {
promise = promise.then(LogoFetchPromis.bind(null, value));
}
});
});
return promise;
}
function LogoFetchPromis(carName, wrapNow) {
// don't create a new Promise here, $.ajax is already a promise
return $.ajax({
url: "/getLogo/" + carName,
dataType: 'text'
}).done(function (response) {
// logos is the section where logos should appear
$('.logos').append(response);
if (wrapNow)
$('.logos').append('<br>');
}).fail(function (response) {
// manage error here is needed
});
}
I commented the changes but the most important is promise.then(LogoFetchPromis(value));. You're calling LogoFetchPromis() directly, that's why your logos show randomly, all the $.ajax are actually done at once. In the fixed code (promise.then(LogoFetchPromis.bind(null, value)), bind() returns a Function that is not executed until promise is resolved so LogoFetchPromis() will be called one at a time and your logos should be displayed in order.

Issue in returning data retrieved from DB queries called in the loop

I making multiple mongoDB queries in loop. and want to send the all results as one data array.But when I simple use the return for send the data it simply return undefined and do not wait for results of all DB request. I also tried to use q.moulde but same issue.
Code:
var getPrayerInCat = function(data){
var result ;
var finalData = [];
if(data.length >0){
data.forEach(function(data2){
var id= data2.id;
Prayer.find({prayerCat:id},function(err,prayer){
var deferred = Q.defer()
if (err) { // ...
console.log('An error has occurred');
// res.send(err);
result= finalData = err
} else {
if(!prayer){
// console.log(data2.id+'--0');
data2.prayersCount = 0;
result = deferred.resolve(finalData.push(data2))
} else {
// console.log(data2.id+'--'+prayer.length);
data2.prayersCount = prayer.length;
// console.log(prayer)
result = deferred.resolve(finalData.push(data2))
} // else for data forward
}
deferred.promise;
})
// deferred.resolve(finalData);
})
/*if(finalData.length > 0) { return finalData;}*/
}
}
finalData is returned undefined.
Let's start with the general rule for using promises:
Every function that does something asynchronous must return a promise
Which functions are these in your case? It's getPrayerInCat, the forEach callback, and Prayer.find.
Hm, Prayer.find doesn't return a promise, and it's a library function so we cannot modify it. Rule 2 comes into play:
Create an immediate wrapper for every function that doesn't
In our case that's easy with Q's node-interfacing helpers:
var find = Q.nbind(Prayer.find, Prayer);
Now we have only promises around, and do no more need any deferreds. Third rule comes into play:
Everything that does something with an async result goes into a .then callback
…and returns the result. Hell, that result can even be a promise if "something" was asynchronous! With this, we can write the complete callback function:
function getPrayerCount(data2) {
var id = data2.id;
return find({prayerCat:id})
// ^^^^^^ Rule 1
.then(function(prayer) {
// ^^^^^ Rule 3
if (!prayer)
data2.prayersCount = 0;
else
data2.prayersCount = prayer.length;
return data2;
// ^^^^^^ Rule 3b
});
}
Now, we have something a bit more complicated: a loop. Repeatedly calling getPrayerCount() will get us multiple promises, whose asynchronous tasks run in parallel and resolve in unknown order. We want to wait for all of them - i.e. get a promise that resolves with all results when each of the tasks has finished.
For such complicated tasks, don't try to come up with your own solution:
Check the API of your library
And there we find Q.all, which does exactly this. Writing getPrayerInCat is a breeze now:
function getPrayerInCat(data) {
var promises = data.map(getPrayerCount); // don't use forEach, we get something back
return Q.all(promises);
// ^^^^^^ Rule 1
}
If we needed to do anything with the array that Q.all resolves to, just apply Rule 3.

Categories