Ajax リクエストの順番を直感的に記述可能な uu.ajax.queue を追加
uu.ajax.queue は順番や待ち合わせなどの規則を指定した Ajax リクエストを可能にします。
uu.ajax.queue(cmd, [url, ...], [option, ...], [fn, ...], lastfn, ngfn)
- cmd には "0+1+2" や "a>b+c>d" などのような規則を文字列で記述します
- option オプションを Hash で指定します。省略可能で、デフォルトは {} です。
- option.data - Mix(= null): request data
- option.header - Array(= []): [(key, value), ...]
- option.timeout - Number(= 10): unit sec
- option.xhr - Object(= void 0): overrideMimeType
- option.method - String(= "GET" or "POST"):
- fn 各リクエスト成功時のコールバック関数を指定します。省略可能です。
- lastfn 全リクエスト成功時のコールバック関数を指定します。省略可能です。
- lastfn は lastfn([{ code, ...}, ...]) の形でコールバックします。 { code, ...} の内容は fn と同じです
- ngfn いずれかのリクエスト失敗時のコールバック関数を指定します。省略可能です。
- ngfn は ngfn({ code, ...) の形でコールバックします。 { code, ...} の内容は fn と同じです
- 複数のリクエストが失敗しても最初のエラーしか捕捉しません。
実装
// uu.ajax.queue - request queue // uu.ajax.queue("a+b>c", [url, ...], [option, ...], [fn], lastfn, ngfn) function uuajaxqueue(cmd, // @param String: "0>1", "0+1", "0+1>2>3" urlary, // @param Array: [url, ...] optary, // @param Array: [option, ...] fnary, // @param Array: [fn, ...] lastfn, // @param Function(= void 0): lastfn([{...}, ... ]) ngfn) { // @param Function(= void 0): ngfn({...}) _uuajaxqueue(cmd, urlary, optary, fnary, lastfn || uuvain, ngfn || uuvain, []); } // inner - function _uuajaxqueue(cmd, urlary, optary, fnary, lastfn, ngfn, rv) { function _nextq(r) { _uuajaxqueue(cmd, urlary, optary, fnary, lastfn, ngfn, rv.concat(r)); // recursive call } if (!cmd) { lastfn(rv); // finish return; } var ary = cmd.split(""), v, url = [], opt = [], fn = []; cmd = ""; while ( (v = ary.shift()) ) { // v = "0" url.push(urlary.shift()) opt.push(uumix(optary.shift() || {}, { id: v })); // set option.id fn.push(fnary.shift() || uuvain); if (ary.shift() === ">") { // v = "+" or ">" cmd = ary.join(""); // rebuild command, "0+1>2>3" -> "2>3" break; } } url.length && _uuajaxparallel(url, opt, fn, _nextq, ngfn); } // inner - ajax parallel load function _uuajaxparallel(urlary, optary, fnary, lastfn, ngfn) { function _nextp(hash) { var idx = uuaryindexof(atom, hash.guid); // = Array.indexOf rv[idx] = hash; ++n >= iz && !run++ && lastfn(rv); // fn([{...}, ..]) } function _error(hash) { !run++ && ngfn(hash); } var rv = [], atom = [], i = 0, iz = urlary.length, n = 0, run = 0; for (; i < iz; ++i) { atom.push(uu.ajax(urlary[i], optary[i], fnary[i], _error, _nextp)); } }
デモ
http://pigs.sourceforge.jp/blog/20091221/uupaa.ajax.queue.htm
<!doctype html><html><head><meta charset="UTF-8" /><title></title> <script src="uupaa.js"></script> <script> function ok(ary) { uu.puff(ary); } function ng(hash) { uu.puff(hash); } // 1+2>3の順にアクセスする function queue1() { uu.ajax.queue("1+2>3", ["1.php", "2.php", "3.php"], [], [function() { window.status += "1"; }, function() { window.status += "2"; }, function() { window.status += "3"; }], ok, ng); } // 3>1>2の順にアクセスする function queue2() { uu.ajax.queue("3>1>2", ["3.php", "1.php", "2.php"], [], [function() { window.status += "3"; }, function() { window.status += "1"; }, function() { window.status += "2"; }], ok, ng); } // 1+2+3の順にアクセスする function queue3() { uu.ajax.queue("1+2+3", ["1.php", "2.php", "3.php"], [], [function() { window.status += "1"; }, function() { window.status += "2"; }, function() { window.status += "3"; }], ok, ng); } // 1+2+3>0の順にアクセスする function queue4() { uu.ajax.queue("1+2+3>0", ["1.php", "2.php", "3.php", "0.php"], [], [function() { window.status += "1"; }, function() { window.status += "2"; }, function() { window.status += "3"; }, function() { window.status += "0"; }], ok, ng); } // 0+1+0>2の順にアクセスする function queue5() { uu.ajax.queue("0+1+0>2", ["0.php", "1.php", "0.php", "2.php"], [], [function() { window.status += "0"; }, function() { window.status += "1"; }, function() { window.status += "0"; }, function() { window.status += "2"; }], ok, ng); } // 0+1+0>2の順にアクセスする // HEAD function queue6() { uu.ajax.queue("0+1+0>2", ["0.php", "1.php", "0.php", "2.php"], [ { method: "HEAD" }, { method: "GET" }, { method: "POST" }, { method: "HEAD" }, ], [function() { window.status += "0"; }, function() { window.status += "1"; }, function() { window.status += "0"; }, function() { window.status += "2"; }], ok, ng); } // 0+1+9>2の順にアクセスする( 9.php が存在しないためエラーになる) function queue7() { uu.ajax.queue("0+1+9>2", ["0.php", "1.php", "9.php", "2.php"], [ { method: "HEAD" }, { method: "GET" }, { method: "GET" }, { method: "HEAD" }, ], [function() { window.status += "0"; }, function() { window.status += "1"; }, function() { window.status += "9"; }, function() { window.status += "2"; }], ok, ng); } </script></head><body> <input type="button" value="queue(1+2>3)" onclick="queue1()" /> <input type="button" value="queue(3>1>2)" onclick="queue2()" /> <input type="button" value="queue(1+2+3)" onclick="queue3()" /> <input type="button" value="queue(1+2+3>0)" onclick="queue4()" /> <input type="button" value="queue(0+1+0>2)" onclick="queue5()" /> <input type="button" value="queue(0+1+0>2, HEAD)" onclick="queue6()" /> <input type="button" value="queue(0+1+9>2, ERR)" onclick="queue7()" /> </body></html>
複数のストリームで待ち合わせをしたい場合は
1 → 2 → 3 ┐ ├→ A → B a → b → c ┘
ストリーム(1 > 2 > 3)と(a > b > c)を読み込み後に、(A > B)を読み込みたい場合は以下のようにします。
var resultArray = [], run = 0; function job() { uu.ajax.queue("1>2>3", [...], [], [], wait, ng); uu.ajax.queue("a>b>c", [...], [], [], wait, ng); } function wait(ary) { resultArray = [].concat(resultArray, ary); if (++run === 2) { uu.ajax.queue("A>B", [...], [], [], ok, ng); } } function ok(ary) { resultArray = [].concat(resultArray, ary); // リクエスト結果を結合 } function ng(hash) { uu.puff(hash); // エラー }
なにやらスマートさが大幅ダウンです。
このようなコードが必要になったらちょっと設計を見直すべきかもしれません。
問題がなければ、この変更は次回のリリースから適用されます。
反省会
- コードを読めば分かるように、"+" を認識していないため、(1-2>3) も(1+2>3) も同じ結果になります。
- 仕様をしっかり明示できていれば、これぐらいの手抜き(エラーチェック分のコード削り)はむしろ有りだと思うんだ。