问题
I'm parsing data returned from Eventbrite's API, and forming objects from the name of the event and any img tags scraped from the entirety of data response.
for (var i = 1, l = rawEventbriteData.length; i < l; i++){
var eventObject = {
name: rawEventbriteData[i].event.title,
images: []
};
var jsdom = require('jsdom');
var arrayOfImgs = [];
jsdom.env({
html: rawEventbriteData[i].event.description,
scripts: ["http://code.jquery.com/jquery.js"],
done: function(errors, window) {
window.$('img').each(function(){
var imgSrc = window.$(this).attr('src');
console.log(imgSrc);
eventObject.images.push(imgSrc);
});
}
});
}
I log out the result:
the events are [ { name: 'Sense 5K - Austin Pre-Registration', images: [] },
{ name: 'Tech Snack - Fostering Student Research', images: [] },
{ name: 'Coffee,Dessert and Chat', images: [] },
{ name: 'Texas Table Tennis!', images: [] },
{ name: '#3and3 w/ @FortyCreek_John', images: [] },
{ name: 'BUSINESS COACHING | CONVERSE-OVER-COFFEE (Austin)',
images: [] },
{ name: 'Tiny House in Austin, Texas', images: [] },
{ name: 'Fastest House Tour in America', images: [] },
{ name: 'Texas NORML\'s Monthly Open Meeting, Dreadneck Wednesday and Smokin\' Stand-Up',
images: [] },
{ name: 'Amazing Scavenger Hunt Adventure-Austin', images: [] } ]
https://evbdn.eventbrite.com/s3-s3/eventlogos/90039995/about.png
https://evbdn.eventbrite.com/s3-s3/eventlogos/90039995/bluedawntour1.jpg
And we can see that the each event object has the name property as expected, but the array of images remains empty. The image sources do end up logging at the end of the terminal output (called within the jsdom function), but clearly these urls are not available when they are needed.
I want to end up with eventObjects that have the name as they currently do, as well as the array of images that currently doesn't come back in time. What is a good approach for solving this?
回答1:
This is the classic closure problem. The function you're passing as the done
handler has an enduring reference to the eventObject
variable (and there's only one, it's not loop-specific), so only the last one gets filled in. (This is usually what you want, but not in this case.)
You can work around it with a builder function that closes over something else (usually an argument):
for (var i = 1, l = rawEventbriteData.length; i < l; i++){
var eventObject = {
name: rawEventbriteData[i].event.title,
images: []
};
var jsdom = require('jsdom');
var arrayOfImgs = [];
jsdom.env({
html: rawEventbriteData[i].event.description,
scripts: ["http://code.jquery.com/jquery.js"],
done: buildDoneHandler(eventObject)
});
}
function buildDoneHandler(evtobj) {
return function(errors, window) {
window.$('img').each(function(){
var imgSrc = window.$(this).attr('src');
console.log(imgSrc);
evtobj.images.push(imgSrc);
});
};
}
Note that the functon returned by buildDoneHandler
closes over and uses evtobj
(the argument we give buildDoneHanlder
) rather than eventObject
. Since the argument is specific to each call to buildDoneHandler
, we update the right event object.
This can also be done with ES5's Function#bind
although it can get confusing when there are other arguments, and creating functions that you bind
in a loop is wasteful (not that it usually matters):
for (var i = 1, l = rawEventbriteData.length; i < l; i++){
var eventObject = {
name: rawEventbriteData[i].event.title,
images: []
};
var jsdom = require('jsdom');
var arrayOfImgs = [];
jsdom.env({
html: rawEventbriteData[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);
});
}.bind(null, eventObject)
});
}
Function#bind
returns a function that, when called, will call the original function with a specific this
value and with any further arguments you give it at the beginning of the arguments list.
来源:https://stackoverflow.com/questions/25576279/how-to-synchronously-declare-js-object-and-then-edit-a-property-of-the-object-wi