GreaseMonkey の GM_xmlhttpRequest を順番に同期とりながら実行する

GreaseMonkeyGM_xmlhttpRequestで同期通信(モドキ)
http://d.hatena.ne.jp/daftbeats/20070605

GreaseMonkeyGM_xmlhttpRequest は非同期通信なので、1つ目の通信が終わるのを待ってから2つ目の通信を開始する。といった時に困ってしまうことがよくある。

GM_xmlhttpRequest({
    url: "http://ドコカ",
    onload: function(r) {
        GM_xmlhttpRequest({
            url: "http://ドコカ",
            onload: function(r) {
                GM_xmlhttpRequest({
                    url: "http://ドコカ",
                    onload: function(r) {
                        alert("うひょー");
                    }
                });
            }
        });
    }
});

大抵、こんな感じになってしまう。

他にいい方法無いかなぁ。

おじさんも困ってたので、非同期通史の処理を順番に実行してくれるようなものを作ってみた。

使い方

チェーンオブジェクトっていうのを作り、そこに関数を追加していって、最後に一気に実行する。
「this」って書くと、どこからでも chain オブジェクトにアクセスできるので便利!

// チェーンオブジェクトを作る
var chain = new Chain();

// 「addRequestFunction」で他サイトと通信するジョブを追加
// 引数は、GM_xmlhttpRequest の引数に渡すデータを返す関数。ややこしい><
chain.addRequestFunction( function() { return {
    method: 'GET',
    url: 'http://twitter.com/statuses/public_timeline.json',
    onload: function( response ) {
        // this = chain
        this.public_timeline = eval( "(" + response.responseText + ")" );
    }
}});

// 「addFunction」で普通の関数を追加
chain.addFunction( function() {
    // ここでは既に public_timeline にデータがセットされてる。便利!
    // this 経由でアクセスできるよ。
    var div = document.createElement( "div" );
    var txt = document.createTextNode( this.public_timeline[0].user.name );
    div.appendChild( txt );
    document.body.appendChild( div );
});


// 先頭にいた人のユーザー情報を取得してみよう。
chain.addRequestFunction( function() {
    // ここからも this でアクセスできるよ
    var id = this.public_timeline[0].user.screen_name;
    
    // 先頭にいた人のユーザー情報を取得するジョブ
    return {
        method: 'GET',
        url: "http://twitter.com/users/show/" + id + ".json",
        onload: function( response ) {
            this.user = eval( "(" + response.responseText + ")" ); // this = chain
        }
    }
});

chain.addFunction( function() {
    var img = document.createElement( "img" );
    img.src = this.user.profile_image_url;  // this = chain
    document.body.appendChild( img );
});


// 「doChain」で追加してきた関数を次々と実行する。
chain.doChain();

ソース

//
// チェーンを順番に実行して行くクラス。
//
function Chain() {
    // コンストラクタ
    this.jobs = [];
    this.container = {};
    
    // チェーンに GM_xmlhttpRequest の引数を返す関数を追加する。
    this.addRequestFunction = function( f ) {
        this.jobs.push({ type: 'request', func: f })
    };
    
    // チェーンに普通の関数を追加する。
    this.addFunction = function( f ) {
        this.jobs.push({ type: 'function', func: f })
    };
    
    // 先頭のジョブを返す
    this.shift = function() {
        return this.jobs.shift();
    };
    
    // チェーンを順番に実行して行く。
    this.doChain = function () {
        // 先頭のジョブを取り出す。何もなかったらおしまい。
        var job = this.jobs.shift();
        if( ! job ) return;
        
        if( job.type == 'function' ) {
            // ただの関数ならそのまま実行する。
            job.func.apply( this );
            // 終わったら次のジョブを実行する。
            this.doChain();
        } else if( job.type == 'request' ) {
            // リクエストだったら GM_xmlhttpRequest のパラメーターをちょっと改ざんして実行する。
            var obj  = job.func.apply( this );
            obj.chain = this;
            if( obj.onload ) {
                // onloadを改ざん。関数を実行した後に次のジョブを実行してもらう。
                obj.$onload = obj.onload;
                obj.onload = function( response ) {
                    obj.$onload.apply( this.chain, [response] );
                    this.chain.doChain();
                }
            }
            GM_xmlhttpRequest( obj );
        }
    };
}

うっひゃー!こいつぁ便利だ。
って思ってたんだけど、

            var obj  = job.func.apply( this );
            obj.chain = this;
            if( obj.onload ) {
                obj.$onload = obj.onload;
                obj.onload = function( response ) {
                    obj.$onload.apply( this.chain, [response] );
                    this.chain.doChain();
                }
            }

obj.onload にセットした無名関数の中で「obj」にアクセスできるってことは、obj.onloadは「obj」への参照を持っているってことで、これって循環参照ってやつになっちゃうんじゃないのか?
DOMツリーと循環してなかったら大丈夫なのか? Greasemonkeyは仕事が終わったらキレイに後片付けをして帰ってくれるから何も心配いらないのか?!
うわー わからない。不安だ。