[Dojo-interest] Advice on closures, and retaining variable values inside async handlers e.g. xhrGet?

Peter Higgins dante at dojotoolkit.org
Fri Aug 5 07:41:56 EDT 2011


Also, this is a bit wasteful too, but works:

for(idx in someObject){
     (function(idx){
         // anything you do in here will use the 'local' idx, as if 
you'd passed it to sendXhr()
         // but this way requires a new "iife" for each iteration, 
whereas creating a function
         // prior only requires you call that function. because all this 
is is a function that is
         // being defined and called all at once.
     })(idx);
}

With dojo.forEach. This is the longhand::

var arr = [1,2,3];
for(var i = 0, l = arr.length; i < l; i++){
     (function(item, index, ar){
             setTimeout(function(){
                     // what will "i" be, versus "index" when this was 
invoked
                     console.log(item, index, i);
             }, Math.random() * 1000);
     })(arr[i], i, arr);
}

vs:

var arr = [1,2,3];
dojo.forEach(arr, function(item, index, ar){
         // we're making this function once, and know our values
         // inside will be scoped/local/whatevs
});

the implementation of forEach shows how the "iife" is the same:

var forEach = function(arr, callback, context){
     context = context || window;
     for(var i = 0; i < arr.length; i++){
             // call callback(item, index, array) in scope of 'context' 
if passed
             callback.call(context, arr[i], i, arr);
     }
}

On 8/5/11 7:33 AM, Peter Higgins wrote:
> Passing the value as-is to a function avoids this issue:
>
> var idx, container = "a container",
>      someObject = { a:1, b: 2 }
>
> function sendXhr(idx, someObject){
>     // idx and someObject are local here, can't access "parent" versions
>     // though 'someObject' is var by reference, so changes to it reflect
>     // in all references.
>     dojo.xhrGet({
>             url:...,
>             load: function(){
>                 console.log("load for", idx, " has value ", 
> someObject[idx]);
>                 // re: reference note. later, you can check 
> someObject.a_complete to see if this
>                 // load callback finished.
>                 someObject[idx + "_complete"] = true;
>             }
>     });
> }
>
> for(idx in someObject){
>     sendXhr(idx, someObject);
> }
>
> On 8/5/11 1:34 AM, Nick Fenwick wrote:
>> Just wondered if people would like to share their thoughts and 
>> personal 'best practise'.  If this has come up a lot before, feel 
>> free to link to articles giving good answers.
>>
>> A typical problem with javascript closures and asynchronous method 
>> invocations is that loop variables are encountered by the 
>> asynchronous handles as the "last known" value.  This is because the 
>> closure they are associated with has had the loop variable advanced 
>> to the final iteration before the handler is invoked and gets a 
>> chance to inspect the loop variable.
>>
>> My apologies if Thunderbird does something horrible to the syntax 
>> highlighting below.  Both examples are in a dojo-sandbox that you can 
>> run for clarity.
>>
>> http://dojo-sandbox.net/public/82962/0 
>> <http://dojo-sandbox.net/public/82962/1>
>> varidx;
>> varcontainer="a container";
>>
>> varsomeObject={a:1,b:2};
>>
>> for(idxinsomeObject){
>>
>> dojo.xhrGet({
>> url:'/lib/dojo-1.6.1/dojo/dojo.js',// just something that works
>> load:function(data){
>> console.log("Here I can see "+container+", and idx "+idx);
>> }
>> });
>>
>> }
>>
>> That prints "b" both times in the load handler, because the handler 
>> is invoked twice after the for() loop has exited.  The handler has 
>> the outer scope available to it via closure chaining, so it can see 
>> idx and container, but it doesn't see their values at the time 
>> dojo.xhrGet was invoked.
>>
>> So, how do people get around this?
>>
>> I currently use a 'context' object to copy current values of 
>> important variables, but this seems very wasteful.  This next example 
>> also logs before and after calling xhrGet, just to make it clear that 
>> the load handler is invoked after the loop has finished.
>>
>> http://dojo-sandbox.net/public/82962/1
>> varidx;
>> varcontainer="a container";
>>
>> varsomeObject={a:1,b:2};
>>
>> for(idxinsomeObject){
>>
>> console.log("Calling xhrGet with idx "+idx+"...");
>> varcontext={
>> idx:idx
>> };
>>
>> dojo.xhrGet({
>> url:'/lib/dojo-1.6.1/dojo/dojo.js',// just something that works
>> load:dojo.hitch(context,function(data){
>> console.log("Here I can see "+container+", and idx "+idx+" with 
>> context idx "+this.idx);
>> })
>> });
>>
>> console.log("Finished xhrGet with idx "+idx+".");
>>
>> }
>>
>> Output:
>> Calling xhrGet with idx a...
>> Finished xhrGet with idx a.
>> Calling xhrGet with idx b...
>> Finished xhrGet with idx b.
>> Here I can see a container, and idx b with context idx a
>> Here I can see a container, and idx b with context idx b
>>
>> That prints the correct 'a' and 'b' values from the context object, 
>> known as 'this' within the load handler due to being dojo.hitch'ed.
>>
>> How would people improve on this general strategy?
>>
>> Nick
>>
>>
>> ________________________________________________________
>> Dojotoolkit:http://dojotoolkit.org
>> Reference Guide:http://dojotoolkit.org/reference-guide
>> API Documentation:http://dojotoolkit.org/api
>> Tutorials:http://dojotoolkit.org/documentation
>>
>> Dojo-interest at mail.dojotoolkit.org
>> http://mail.dojotoolkit.org/mailman/listinfo/dojo-interest
>
>
>
> ________________________________________________________
> Dojotoolkit: http://dojotoolkit.org
> Reference Guide: http://dojotoolkit.org/reference-guide
> API Documentation: http://dojotoolkit.org/api
> Tutorials: http://dojotoolkit.org/documentation
>
> Dojo-interest at mail.dojotoolkit.org
> http://mail.dojotoolkit.org/mailman/listinfo/dojo-interest

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.dojotoolkit.org/pipermail/dojo-interest/attachments/20110805/d4e0ee8d/attachment.htm 


More information about the Dojo-interest mailing list