Reputation: 869
After a few hours of thinking and googleing, I seem to be unable to find a solution to this stupid simple problem!
I'm trying to a create an array of JSON objects in order to insert them into DB. But due to Async aspect of Nodejs it has become a little bit complicated.
What I have:
function foo (arg) {
request({url: arg}, function(err, response, results){
var chunk = [];
var r = JSON.parse(results);
(r.bar).forEach(function(item){
geocoder.reverse({lat: item.lat, lon: item.lng}, function(err, res){
chunk.push(res[0].formattedAddress;
});
});
console.log(chunk); \\ This is eventually empty!
insertData(chunk); \\ useless due to chunk being empty!
});
I was wondering how could I make this happen (construct an array and insert it into DB)?
I'm desperate please help.
Thanks in advance,
Upvotes: 0
Views: 1333
Reputation: 708206
Here's a solution using promises to manage the async operations and retain order of multiple results:
var Promise = require('bluebird');
var requestAsync = Promise.promisify(request);
var geocoder.reverseAsync = Promise.promisify(geocoder.reverse);
function foo(arg) {
return requestAsync({url: arg}).spread(function(response, results) {
var r = JSON.parse(results);
return Promise.map(r.bar, function(item) {
return geocoder.reverseAsync({lat: item.lat, lon: item.lng}).then(function(res) {
return res[0].formattedAddress;
});
});
});
}
foo(something).then(function(addresses) {
// addresses will be an array of addresses
insertData(addresses);
}, function(err) {
// some error occurred
});
Here's an explanation for how it works:
requestAsync()
and use .spread()
instead of .then()
to more easily separate out the two values returnedPromise.map()
to iterate the array of data for us and return a single promise that represents all the sub-promises so we know when all the geocoder operations are done.Promise.map()
iterator callback, call geocoder.reverseAsync()
and use .then()
to access the result.geocoder
result to return the formattedAddress
which will become the returned value from each promise in Promise.map()
foo()
use .then()
to set up your success and error handlers. In the success handler, you can access the array of addresses in order.FYI, if you are concerned about running into rate limiting issues about how many simultaneous geocoder calls you may have running at the same time, you can add a concurrency option to Promise.map()
like this:
return Promise.map(r.bar, function(item) {
...
}, {concurrency: 3});
Upvotes: 1
Reputation: 292
I recommend installing the async module, as its invaluable for node.js
First:
npm install --save async
Then:
var async = require('async');
function foo (arg) {
request({url: arg}, function(err, response, results){
var r = JSON.parse(results);
async.map(r.bar, function eachItem (item, cb) {
geocoder.reverse({lat: item.lat, lon: item.lng}, function(err, res){
cb(null, res[0].formattedAddress);
});
},
function done (err, result) {
console.log(result); \\ this will be your ordered array
insertData(result); \\ no longer useless!
});
});
Upvotes: 2
Reputation: 104805
You have to wait for the geocoder.reverse
function to finish for each r.bar
- create a counter, increment on each complete call, then when the final call is done, insertData
!
function foo (arg) {
request({url: arg}, function(err, response, results){
var chunk = [];
var r = JSON.parse(results);
var totalCalls = r.bar.length;
var completedCalls = 0;
(r.bar).forEach(function(item){
geocoder.reverse({lat: item.lat, lon: item.lng}, function(err, res){
chunk.push(res[0].formattedAddress); //missing ) here in your code
completedCalls++;
if (completedCalls == totalCalls) {
insertData(chunk);
}
});
});
});
}
Upvotes: 1