gotin blog

Whatever gotin wanna write

GM_xmlhttpRequestで同期処理っぽく書く方法の検討

追記

Deferredチェーン、非同期処理の逐次実行 - oct inaodu

↑ここで解説されてるDeferredチェーンが数分で考えたgotin方式なんかより全然洗練されてますね。多段になるように改造すれば同じような感じになるんかな。。

本文

JavaScript tailcall... - 冬通りに消え行く制服ガールは✖夢物語にリアルを求めない。 - subtech

↑これは使えますね。
ここではプロセス順をうまく制御して末尾再帰をsetTimeoutで処理するようにしてるところがポイントだと思います。
で、あんまりこれのポイントとは関係がないんですが、GM_xmlhttpRequestだと同期処理ができないけど、同期処理してるみたいに書けないかなぁというのを、↑これをちょっと改造して書いてみました。

// ==UserScript==
// @name           process_test
// @namespace      gomaxfire.dnsdojo.com
// @include        *
// ==/UserScript==

function Process () {
  this.init.apply(this);
}
Process.prototype = {
  init: function () {
    var self = this;
    this.stack = [];
    this._rvalue = undefined;
    this.pause = false;  // trueだと処理を進めない
    setTimeout(function () { self.call() }, 0);
  },

  push:function(f){ // push(というかcall)したときpause状態なら再開するように
    this.stack.push(f);
    if(this.pause){
      var self = this;
      setTimeout(function () { self.call() }, 0);
    }
  },

  p: function (f) {
    this.stack.unshift({f:f, go:true}); //goで処理を進めるかどうかを制御
    return this;
  },

  pstop: function (f) { 
    this.stack.unshift({f:f, go:false}); //pstopだと実行後pause状態に
    return this;
  },

  call: function () {
    var self = this;
    var pr =this.stack.pop();
    var f = pr.f;
    var thisObj = {};
    thisObj.call = function (f, args) {
      args = Array.prototype.slice.call(arguments);
      f    = args.shift();
      self.push({
        f:function () {
          return f.apply(thisObj, args);
        },
        go:true
      });
      return "this";
    };
    this._rvalue = f.call(thisObj, this._rvalue);
    if (this.stack.length) {
      if(pr.go){ 
        setTimeout(function () { self.call() }, 0);
      } else {
        self.pause = true;  // pstopの場合はpause状態に
      }
    }
  }
};

function log(m){console.log(m);}

var ps = new Process();
ps.p(function(){
  log("start");
}).pstop(function(){  // GM_xmlhttpRequestの実行後pause状態に
  log("xhr start");
  var self = this;
  GM_xmlhttpRequest({
    url:document.location.href,
    method:"GET",
    onload:function(xhr){
      self.call(function(){return xhr}); //もどってきたら再開(xhrを次の処理に渡す)
    }
  });
}).p(function(xhr){
  log(xhr.responseText);  // ↑で返されるxhrをつかって処理
  log("end");
});

結果:

start
xhr start
(開いているページのソース)
end

目的は達成できてるっぽいけど穴はありそう。もっと美しくやるほうほうもあるかな。