This is an extension to my previous question, which received a very explanatory answer. It turns out that I did not provide enough context to my app to make the question useful enough for my actual situation.
Here is a route within my Express app:
var eventbriteService = require('../apiRequests/eventbriteService');
var queue = require('queue-async');
app.get('/events', function (req, res, next) {
queue()
.defer(eventbriteService.getEventbriteEvents)
.await(function(err, data) {
if (err) {return next(err);}
console.log("here are the events from routes" ,data);
});
});
This route calls the following service:
exports.getEventbriteEvents = function (cb) {
var eventbriteEvents = [];
request({
uri: "https://www.eventbrite.com/json/event_search?app_key=R3DQQXPXBTSMUWVNOV&city=Austin&date=2014-10-01&2014-10-15",
method: "GET", timeout: 10000, followRedirect: true, maxRedirects: 10,
}, function(err, response, body){
if (err) return cb(err);
try {
var eventsJSON = JSON.parse(body);
var eventsWithAllFields = eventsJSON.events;
var totalEventsNumber = parseInt(eventsWithAllFields[0].summary.total_items);
for (var i = 1, l = eventsWithAllFields.length; i < l; i++) {
var eventObject = {
name: eventsWithAllFields[i].event.title,
images: []
};
var jsdom = require('jsdom');
var arrayOfImgs = [];
jsdom.env({
html: eventsWithAllFields[i].event.description,
scripts: ["http://code.jquery.com/jquery.js"],
done: function(evtobj, errors, window) {
window.$('img').each(function(){
var imgSrc = window.$(this).attr('src');
console.log(imgSrc);
evtobj.images.push(imgSrc);
});
eventbriteEvents.push(evtobj);
}.bind(null, eventObject)
});
}
} catch(err) {
console.log(err);
return cb(err);
}
console.log(eventbriteEvents);
cb(null, eventbriteEvents);
});
};
This is my console output:
{}
[]
here are the events from routes []
https://evbdn.eventbrite.com/s3-s3/eventlogos/90039995/about.png
https://evbdn.eventbrite.com/s3-s3/eventlogos/90039995/bluedawntour1.jpg
The code is executing in the following order:
- eventbrite service logs the array of eventbrite objects (empty)
- route logs array of eventbrite objects (empty)
- eventbrite service uses jsdom to find the existing imgs in the html
Obviously this is out of sync, and I am quite confused on rectifying this, considering the many layers of callbacks, closures, and queue-async.
I'm using the queue-async library to define a callback in the route which ends up being populated by the eventbrite service. This was working fine until I recently added the jsdom html-parsing functionality. In addition to solving this immediate problem, I am looking for help structuring my mental model of callbacks, closures, and synchronous code.
The problem is that you're calling your callback when your
for
loop is finished, but thefor
loop is calling an asynchronous function (jsdom.env
) on each pass of its loop. What ends up happening is thefor
loop finishes looping before the functions it calls are complete.What you need is something that will call a callback when all those asynchronous functions are complete. Since you're already using
queue-async
elsewhere, let's just use that (see comments in modified code):