How to synchronously declare JS object and then edit a property of the object with data that is dependent on a concurrent operation

夙愿已清 提交于 2019-12-11 21:10:45

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!