For anybody having scope issues with GM_xmlhttpRequest

Subscribe to For anybody having scope issues with GM_xmlhttpRequest 5 posts, 4 voices

 
Adrian Scriptwright

I'm sure quite a few of us have come across the issue of not being able to easily pass variables to time-delayed functions, such as GM_xmlhttpRequest or setTimeout. Especially if these functions are run inside of a recurring function, or inside a loop. So I've created an object class you can use with these functions:

function Scope(o) {
	var scope = this;
	for (a in o)
		this[a] = o[a];
	
	this.callback = function(r) {scope.func.call(scope, r);};
}

This simple class saved me from a lot of headaches with incorrect values.

To use it, simply follow the example below:

// you may use as many variables as you'd like, but `func' MUST be set to your callback function
var scope = new Scope({myVar:some_variable, func:myCallback});

GM_xmlhttpRequest({
  method:'GET',
  url:'http://www.somewhere.com/something.xml',
  onload:scope.callback
});

function myCallback(request) {
  alert(this.myVar); // variables from the Scope object are now found in the `this' object
  ...
}

This works because the variables are copied to the object, thus retaining their current values. And the object will never lose scope as long as the callback function is referenced by the GM_xmlhttpRequest handler.

Enjoy!

 
Henrik N Admin

That's useful.

Also see http://userscripts.org/forums/1/topics/138#post..., though the above is perhaps the tidier solution. I still need to read up on closures/binding in JS.

 
Johan Sundström Scriptwright

A great resource on the theory and practice of lexical closures and partial application is Brockman's Object-Oriented Event Listening through Partial Application in JavaScript. The title sets out to solve the same kind of issue under another set of circumstances (event listeners), but the solution and all understanding of the problem applies to those circumstances and yours alike. You'll have this problem in many similar situations until you understand how closures work in javascript, and then be rid of the issue.

Another elegant solution proposed there is turning your recipe into

Function.prototype.bind = function( thisObject ) {
  var method = this;
  var oldargs = [].slice.call( arguments, 1 );
  return function () {
    var newargs = [].slice.call( arguments );
    return method.apply( thisObject, oldargs.concat( newargs ));
  };
}

GM_xmlhttpRequest({
  method:'GET',
  url:'http://www.somewhere.com/something.xml',
  onload:myCallback.bind( { myVar:some_variable } )
});

// variables from the {...} literal above are now found in the `this' object:
function myCallback( request ) {
  alert( this.myVar );
  ...
}

This also allows you to pass additional function arguments to myCallback, bound in before the request argument, if you'd like to, perhaps like this:

var url = 'http://www.somewhere.com/something.xml';
GM_xmlhttpRequest({
  method:'GET',
  url:url,
  onload:myCallback.bind( {}, url, some_variable )
});

// 2nd onward argument to bind get prepended to the xhr callback's args:
function myCallback( url, myVar, request ) {
  alert( myVar );
  ...
}

 
Adrian Scriptwright

Johan, that solution is wonderful, as I did notice the potential conflict involving event listeners with my method. That way should work quite well under most circumstances.

 
lazyttrick Scriptwright

bump for relevance...

another option is

while(foo){
    request(a, b);
}

function request(a, b){
   GM_xmlhttpRequest({
        method:'GET',
        url:"http://...",
        onload: doIt(a,b)
   });
}

function doIt(a,b){
   //...
}