I have a couple of APIs I need to call to collect and merge information.
I make the first API call and, based on the result, I make several calls to the second one (in a loop).
Since http requests are asynchronous I'm loosing the information. By the time the second step is finished the server (nodejs) already sent the response back to the client.
I've already tried to, somehow, use the callback functions. This managed to keep the response to the client waiting but the information of the second call was still lost. I guess somehow the variables are not being synchronized.
I also did a quick test with away/async but my Javascript mojo was not enough to make it run without errors.
/* pseudo code */
function getData(var1, callback){
url= "http://test.server/bla?param="+var1;
request.get(url, function (error, response, body){
var results = [];
for(var item of JSON.parse(body).entity.resultArray) {
var o = {};
o['data1'] = item.data1;
o['data2'] = item.data2;
o['data3'] = item.data3;
getSecondStep(o, function(secondStepData){
//console.log("Callback object");
//console.log(o);
o['secondStepData'] = secondStepData;
});
results.push(o);
}
callback(results);
});
}
function getSecondStep(object, callback){
url = "http://othertest.server/foobar?param=" + object.data1;
request.get(url, function (error, response, body){
var results = [];
if(response.statusCode == 200){
for(var item of JSON.parse(body).object.array) {
var o = {}
o['data4'] = item.data4;
o['data5'] = item.data5;
results.push(o);
}
callback(results);
}
});
}
What I would like is to be able to collect all the information into one JSON object to return it back to the client.
The client will then be responsible for rendering it in a nice way.
I recommend using the async / await pattern with the request-promise-native library.
This makes API calls really easy to make and the code is cleaner when using this pattern.
In the example below I'm just calling a httpbin API to generate a UUID but the principle applies for any API.
const rp = require('request-promise-native');
async function callAPIs() {
let firstAPIResponse = await rp("https://httpbin.org/uuid", { json: true });
console.log("First API response: ", firstAPIResponse);
// Call several times, we can switch on the first API response if we like.
const callCount = 3;
let promiseList = [...Array(callCount).keys()].map(() => rp("https://httpbin.org/uuid", { json: true }));
let secondAPIResponses = await Promise.all(promiseList);
return { firstAPIResponse: firstAPIResponse, secondAPIResponses: secondAPIResponses };
}
async function testAPIs() {
let combinedResponse = await callAPIs();
console.log("Combined response: " , combinedResponse);
}
testAPIs();
In this simple example we get a combined response like so:
{
{
firstAPIResponse: { uuid: '640858f8-2e69-4c2b-8f2e-da8c68795f21' },
secondAPIResponses: [
{ uuid: '202f9618-f646-49a2-8d30-4fe153e3c78a' },
{ uuid: '381b57db-2b7f-424a-9899-7e2f543867a8' },
{ uuid: '50facc6e-1d7c-41c6-aa0e-095915ae3070' }
]
}
}
I suggest you go over to a library that supports promises (eg: https://github.com/request/request-promise) as the code becomes much easier to deal with than the callback method.
Your code would look something like:
function getData(var1){
var url = "http://test.server/bla?param="+var1;
return request.get(url).then(result1 => {
var arr = JSON.parse(body).entity.resultArray;
return Promise.all( arr.map(x => request.get("http://othertest.server/foobar?param=" + result1.data1)))
.then(result2 => {
return {
data1: result1.data1,
data2: result1.data2,
data3: result1.data3,
secondStepData: result2.map(x => ({data4:x.data4, data5:x.data5}))
}
})
});
}
And usage would be
getData("SomeVar1").then(result => ... );
The problem is that you are calling the callback while you still have async calls going on. Several approaches are possible, such us using async/await, or reverting to Promises (which I would probably do in your case).
Or you can, well, call the callback only when you have all the information available. Pseudo code follows:
function getData(var1, callback){
url= "http://test.server/bla?param="+var1;
request.get(url, function (error, response, body){
var results = [];
var items = JSON.parse(body).entity.resultArray;
var done = 0, max = items.length;
for(var item of items) {
var o = {};
o['data1'] = item.data1;
o['data2'] = item.data2;
o['data3'] = item.data3;
getSecondStep(o, function(secondStepData){
//console.log("Callback object");
//console.log(o);
o['secondStepData'] = secondStepData;
results.push(o);
done += 1;
if(done === max) callback(results);
});
}
});
}
(note that since this is pseudo code, I am not checking for errors or handling a possible empty result from request.get(...))
You need to call the callback of first function only when all the second callback functions have been called. Try this changes:
function getData(var1, callback) {
url = "http://test.server/bla?param=" + var1;
request.get(url, function (error, response, body) {
var results = [],count=0;
var arr = JSON.parse(body).entity.resultArray;
for (let [index, value] of arr.entries()) {
var o = {};
o['data1'] = item.data1;
o['data2'] = item.data2;
o['data3'] = item.data3;
getSecondStep(o, function (secondStepData) {
//console.log("Callback object");
//console.log(o);
o['secondStepData'] = secondStepData;
results[index] = o;
count++;
if (count === arr.length) {
callback(results);
}
});
}
});
}
Related
Hey I'm trying to understand functional programming, stay current etc.
I have this gigantic nested node function tha tI'm attempting to refactor into modules. Each function passes an argument to the next, some are async api calls. I've gotten as far as breaking it down into multiple functions but they're still not really modular as each one calls the next function.
I'm wondered if there's a design pattern to totally untangle them so that the names of the other functions don't appear in the functions but can be passed as callbacks or something. Or maybe that's not even appropriate in this case?
I wrote some simpler fake code that approximates my actual problem so you don't have to sift through endless irrelevant specifics:
function1Async();
function function1Async() {
var theArray = [];
var theData = "";
var theInfo = "";
Async.get( /* api */, function(error, data, response) {
if (error) {
function4(); // function call
}
theData = data[0].infos;
for (i in data) {
theInfo = theData[i].info;
theArray.push(theInfo);
}
function2Async(theArray); // function call
});
}
function function2Async(array) {
var theArray = [];
var theObject = {}
var arrayLength = array.length;
for (let i = 0; i < arrayLength; i++) {
search = '"' + array[i] + '"';
T.get(/* api */, {q: trend, search: 50}, function (error, data, response) {
if (error) {
theArray.push("error");
}
else {
theObject = {
"data": data
}
theArray.push(theObject);
}
if (theArray.length == arrayLength) {
function3(theArray); // function call
}
});
}
}
function function3 (array) {
var theArray = array;
if (theArray.includes("error") == true) {
function5(); // function call
}
else {
function4(theArray); // function call
}
}
function function4(array) {
var file = 'files/data.json';
jsonfile.writeFile(file, array, function(err) {
function5(); // function call
});
}
function function5() {
var file = 'files/data.json';
jsonfile.readFile(file, function(err, obj) {
return res.json(obj);
});
}
Feel free to chime in with any suggestions even if they're not totally related to the question. Like I said I'm just trying to understand this stuff altogether. Thanks.
I am trying to perform sql queries based on the callback results in if conditions but i am unable to write the code .so please provide som information in code
app.get('/resell-property', function(req, res) {
var data = {}
data.unit_price_id = 1;
function callback(error, result) {
if (result.count == 0) {
return hp_property_sell_request.create(data)
}
else if (result.count > 0) {
return hp_unit_price.findAll({
where: {
unit_price_id: data.unit_price_id,
hp_property_id: data.property_id,
hp_unit_details_id: data.unit_details_id
}
})
}
}
hp_property_sell_request.findAndCountAll({
where: {
unit_price_id: data.unit_price_id
}
}).then(function (result) {
if (result) {
callback(null, result);
}
});
});
In this how can i write the callbacks for
hp_property_sell_request.create(data) ,hp_unit_price.findAll({
where: {
unit_price_id: data.unit_price_id,
hp_property_id: data.property_id,
hp_unit_details_id: data.unit_details_id
}
})
In that after returning result again i have to handle callbacks and perform this query
if(result.request_id){
return hp_unit_price.findAll({
where:{
unit_price_id:result.unit_price_id,
hp_property_id:result.property_id,
hp_unit_details_id:result.unit_details_id
}
}).then(function (result){
if(result.is_resale_unit==0 && result.sold_out==0){
return Sequelize.query('UPDATE hp_unit_price SET resale_unit_status=1 WHERE hp_unit_details_id='+result.unit_details_id+' and hp_property_id='+result.property_id)
}
})
}
The promise resolve function takes only one input argument, so if you need to pass in multiple stuff, you have to enclose them in a single object. Like, if you have to go with something like:
database.openCollection()
.then(function(collection){
var result = collection.query(something);
var resultObject = { result: result, collection: collection };
})
.then(function(resultObject){
doSomethingSyncronousWithResult(resultObject.result);
resultObject.collection.close();
});
You can't use Promise all if all of your stuff isn't a result of a promise resolve, you might need to go with something like this.
Disclaimer: The code example is a very poor one, but it explains the concept.
I would suggest you to learn about Promises, particularly Bluebird.
You can promisify traditional callback methods.
I would also create model level functions in different files. Here's an example.
parent.js
const db = require("./connections/database"); // connection to database
const getChildForParent = function (parentId, childId, callback) {
db.find({parent: parentId, child_id: childId}, "childrenTable", function(err, result) {
if (err) {
return callback(err);
}
return callback(null, result);
});
};
children.js
const db = require("./connections/database"); // connection to database
const getToysForChild = function (childId, callback) {
db.find({toy_belongs_to: parentId}, "toysTable", function(err, result) {
if (err) {
return callback(err);
}
return callback(null, result);
});
};
Then in controller you can do something like this:
const Bluebird = require("bluebird");
const Parent = require("./parent.js");
const Child = require("./child.js");
// Promisifying adds "Async" at the end of your methods' names (these are promisified)
Bluebird.promisifyAll(Parent);
Bluebird.promisifyAll(Child);
// Just an example.
app.get("/parent/:parentId/children/:childId", function(req, res) {
return Bluebird.try(function() {
return User.getChildForParentAsync(req.params.parentId, req.params.childId);
}).then(function(child) {
return Child.getToysForChildAsync(child.child_id);
}).then(function(toys) {
// Do something with toys.
});
});
Of course you can do much more with this and this is not the only way.
Also you can use Promise.all(). This method is useful for when you want to wait for more than one promise to complete.
Let's say you have a list of urls that you want to fetch and process the results after all the data has been fetched.
var urls = [url1, url2, url3, url4, url5 .......... ];
var Bluebird = require("bluebird");
var request = require("request"); // callback version library
Bluebird.promisifyAll(request);
// create a list which will keep all the promises
var promises = [];
urls.forEach(function(url) {
promises.push(request.getAsync(url1));
});
// promises array has all the promises
// Then define what you want to do on completion.
Bluebird.all(promises).then(function(results) {
// results is an array with result a url in an index
// process results.
});
I would recommend to use Promises to solve that. If you need all results of all Requests, when they are all done Promise.all() will do that for you. Your basic could look like that:
var req1 = new Promise(function(res, rej){
var req = new XMLHttpRequest()
…
req.addEventListener('load', function (e) {
res(e);
})
var req2 = //similar to the above
Promise.all([req1, req2, …]).then(function(values){
//all requests are done here and you can do your stuff
});
You can also use the new fetch api, which creates Promises like so:
var req1 = fetch(…);
var req2 = fetch(…);
Promise.all([req1, re2, …]).then(…);
I have a strange problem, when I push my result in my array, the result isn't at the right position in my array (for example the result instead of being at the index number 1 is at the index 3), and when I re-run my module results change of position randomly in the array .
var cote = function(links, callback) {
var http = require('http');
var bl = require('bl');
var coteArgus = [];
for (i = 0; i < links.length; i ++) {
http.get('http://www.website.com/' + links[i], function(response) {
response.pipe(bl(function(err, data) {
if (err) {
callback(err + " erreur");
return;
}
var data = data.toString()
newcoteArgus = data.substring(data.indexOf('<div class="tx12">') + 85, data.indexOf(';</span>') - 5);
myresult.push(newcoteArgus);
callback(myresult);
}));
});
}
};
exports.cote = cote;
The problem lies in the fact that although the for is synchronous the http.get and the pipe operation are not (I/O is async in nodejs) so the order of the array depends on which request and pipe finishes first which is unknown.
Try to avoid making async operations in a loop, instead use libraries like async for flow control.
I think this can be done in the right order, using async map
Here a sample with map and using request module.
// There's no need to make requires inside the function,
// is better just one time outside the function.
var request = require("request");
var async = require("async");
var cote = function(links, callback) {
var coteArgus = [];
async.map(links, function(link, nextLink) {
request("http://www.website.com/" + link, function(err, response, body) {
if (err) {
// if error so, send to line 28 with a error, exit from loop.
return nextLink(err);
}
var newcoteArgus = body.substring(
body.indexOf("<div class='tx12'>") + 85,
body.indexOf(";</span>") - 5
);
// pass to next link, and add newcoteArgus to the final result
nextLink(null, newcoteArgus);
});
},
function(err, results) {
// if there's some errors, so call with error
if(err) return callback(err);
// there's no errors so get results as second arg
callback(null, results);
});
};
exports.cote = cote;
One more thing, i'm not sure, really what you are doing in the part where you search html content in the responses but there's a really good library to work with JQuery selectors from server side maybe can be useful for you.
Here's how you should call the function
// Call function sample.
var thelinks = ["features", "how-it-works"];
cote(thelinks, function(err, data) {
if(err) return console.log("Error: ", err);
console.log("data --> ", data);
});
I use the node request ajax package. So, i have an loop, in every iteration it makes an request to my server.
// realItems needs the complete value of items assigned
var realItems;
var items = [];
_.forEach(JSON.parse(body), (value, key) => {
request('myurl/' + id, (error, response, body) => {
items = JSON.parse(body)
});
});
How can i bundle all my requests from request package, so I can assign the value of items variable to the realItems at the end?
// edit:
I use react js, so in this case realItems is an state, and i can't trigger it in every loop iteration, because render triggers on every setState
There are a number of ways to approach this. Here's a brute force method that does not preserve the order of the results:
var items = [];
var cnt = 0;
_.forEach(JSON.parse(body), (value, key) => {
++cnt;
request('myurl/' + value.id, (error, response, body) => {
items.push(JSON.parse(body));
// if all requesets are done
if (--cnt === 0) {
// process items here as all results are done now
}
});
});
Here's a version that uses Bluebird promises:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
Promise.promisifyAll(request);
var promises = [];
_.forEach(JSON.parse(body), (value, key) => {
promises.push(request('myurl/' + value.id));
});
Promise.all(promises).then(function(results) {
// all requests are done, data from all requests is in the results array
// and are in the order that the requests were originally made
});
And, here's a little bit simpler Bluebird promises method that uses a Bluebird iterator:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
Promise.promisifyAll(request);
Promise.map(JSON.parse(body), function(value) {
return request('myurl/' + value.id);
}).then(function(results) {
// all requests are done, data is in the results array
});
Is it a requirement that you use the request package? I use async which is similar and comes with a parallel method which does exactly what you're asking -
https://github.com/caolan/async#parallel
example:
async.parallel([
function(callback){
setTimeout(function(){
callback(null, 'one');
}, 200);
},
function(callback){
setTimeout(function(){
callback(null, 'two');
}, 100);
}
],
// optional callback
function(err, results){
// the results array will equal ['one','two'] even though
// the second function had a shorter timeout.
});
I want to call a function after an asynchronous for loop iterating through values of an Javascript object finishes executing. I have the following code
for (course in courses) {
var url = '...' + courses[course];
request(url, (function (course) {
return function (err, resp, body) {
$ = cheerio.load(body);
//Some code for which I use object values
};
})(course));
}
This can be done in vanilla JS, but I recommend the async module, which is the most popular library for handling async code in Node.js. For example, with async.each:
var async = require('async');
var courseIds = Object.keys(courses);
// Function for handling each course.
function perCourse(courseId, callback) {
var course = courses[courseId];
// do something with each course.
callback();
}
async.each(courseIds, perCourse, function (err) {
// Executed after each course has been processed.
});
If you want to use a result from each iteration, then async.map is similar, but passes an array of results to the second argument of the callback.
If you prefer vanilla JS, then this will work in place of async.each:
function each(list, func, callback) {
// Avoid emptying the original list.
var listCopy = list.slice(0);
// Consumes the list an element at a time from the left.
// If you are concerned with overhead in using the shift
// you can accomplish the same with an iterator.
function doOne(err) {
if (err) {
return callback(err);
}
if (listCopy.length === 0) {
return callback();
}
var thisElem = listCopy.shift();
func(thisElem, doOne);
}
doOne();
}
(taken from a gist I wrote a while back)
I strongly suggest that you use the async library however. Async is fiddly to write, and functions like async.auto are brilliant.
A possible simple JS solution would be to do something like this.
var courses = {
lorum: 'fee',
ipsum: 'fy',
selum: 'foe'
};
var keys = Object.keys(courses);
var waiting = keys.length;
function completedAll() {
console.log('completed all');
}
function callOnCourseComplete(course, func) {
console.log('completed', course);
waiting -= 1;
if (!waiting) {
func();
}
}
var delay = 10000;
keys.forEach(function(course) {
var url = '...' + courses[course];
console.log('request', url);
setTimeout((function(closureCourse) {
return function( /* err, resp, body */ ) {
// Some code for which I use object values
callOnCourseComplete(closureCourse, completedAll);
};
}(course)), (delay /= 2));
});
Update: Probably a better Javascript solution would be to use Promises
const courses = {
lorum: 'fee',
ipsum: 'fy',
selum: 'foe',
};
function completedAll() {
console.log('completed all');
}
function callOnCourseComplete(courseName) {
console.log('completed', courseName);
}
let delay = 10000;
const arrayOfPromises = Object.keys(courses).map(courseName => (
new Promise((resolve, reject) => {
const url = `...${courses[courseName]}`;
console.log('request', url);
setTimeout((err, resp, body) => {
if (err) {
reject(err);
}
// Some code for which I use object values
resolve(courseName);
}, (delay /= 2));
}))
.then(callOnCourseComplete));
Promise.all(arrayOfPromises)
.then(completedAll)
.catch(console.error);