[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