treestyletab/modules/jsdeferred.js
Piro / SHIMODA Hiroshi 4dbdefc368 restore closed tab set without blocking (incomplete)
* tree structure doesn't restored. (regression)
* Deferred.wait(0.25) in askUndoCloseTabSetBehavior() should be removed.
2010-11-11 01:14:04 +09:00

534 lines
14 KiB
JavaScript

var EXPORTED_SYMBOLS = ['Deferred'];
var window = {};
var location = { protocol: 'resource:' };
var document = { addEventListener : function() {} };
Components.utils.import('resource://treestyletab-modules/jstimer.jsm', window);
var setTimeout = window.setTimeout;
var clearTimeout = window.clearTimeout;
var setInterval = window.setInterval;
var clearInterval = window.clearInterval;
/* Header::
* JSDeferred
* Copyright (c) 2007 cho45 ( www.lowreal.net )
*
* http://coderepos.org/share/wiki/JSDeferred
*
* Version:: 0.3.0
* License:: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/* Usage (with jQuery)::
*
* $.deferred.define();
*
* $.get("/hoge").next(function (data) {
* alert(data);
* }).
*
* parallel([$.get("foo.html"), $.get("bar.html")]).next(function (values) {
* log($.map(values, function (v) { return v.length }));
* if (values[1].match(/nextUrl:\s*(\S+)/)) {
* return $.get(RegExp.$1).next(function (d) {
* return d;
* });
* }
* }).
* next(function (d) {
* log(d.length);
* });
*
*/
/* function Deferred () //=> constructor
*
* `Deferred` function is constructor of Deferred.
*
* Sample:
* var d = new Deferred();
* // or this is shothand of above.
* var d = Deferred();
*/
/* function Deferred.prototype.next (fun) //=> Deferred
*
* Create new Deferred and sets `fun` as its callback.
*/
/* function Deferred.prototype.error (fun) //=> Deferred
*
* Create new Deferred and sets `fun` as its errorback.
*
* If `fun` not throws error but returns normal value, Deferred treats
* the given error is recovery and continue callback chain.
*/
/* function Deferred.prototype.call (val) //=> this
*
* Invokes self callback chain.
*/
/* function Deferred.prototype.fail (err) //=> this
*
* Invokes self errorback chain.
*/
/* function Deferred.prototype.cancel (err) //=> this
*
* Cancels self callback chain.
*/
function Deferred () { return (this instanceof Deferred) ? this.init() : new Deferred() }
Deferred.ok = function (x) { return x };
Deferred.ng = function (x) { throw x };
Deferred.prototype = {
init : function () {
this._next = null;
this.callback = {
ok: Deferred.ok,
ng: Deferred.ng
};
return this;
},
next : function (fun) { return this._post("ok", fun) },
error : function (fun) { return this._post("ng", fun) },
call : function (val) { return this._fire("ok", val) },
fail : function (err) { return this._fire("ng", err) },
cancel : function () {
(this.canceller || function () {})();
return this.init();
},
_post : function (okng, fun) {
this._next = new Deferred();
this._next.callback[okng] = fun;
return this._next;
},
_fire : function (okng, value) {
var next = "ok";
try {
value = this.callback[okng].call(this, value);
} catch (e) {
next = "ng";
value = e;
if (Deferred.onerror) Deferred.onerror(e);
}
if (value instanceof Deferred) {
value._next = this._next;
} else {
if (this._next) this._next._fire(next, value);
}
return this;
}
};
/* function next (fun) //=> Deferred
*
* `next` is shorthand for creating new deferred which
* is called after current queue.
*/
Deferred.next_default = function (fun) {
var d = new Deferred();
var id = setTimeout(function () { d.call() }, 0);
d.canceller = function () { clearTimeout(id) };
if (fun) d.callback.ok = fun;
return d;
};
Deferred.next_faster_way_readystatechange = ((location.protocol == "http:") && !window.opera && /\bMSIE\b/.test(navigator.userAgent)) && function (fun) {
// MSIE
var d = new Deferred();
var t = new Date().getTime();
if (t - arguments.callee._prev_timeout_called < 150) {
var cancel = false;
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "javascript:";
script.onreadystatechange = function () {
if (!cancel) {
d.canceller();
d.call();
}
};
d.canceller = function () {
if (!cancel) {
cancel = true;
script.onreadystatechange = null;
document.body.removeChild(script);
}
};
document.body.appendChild(script);
} else {
arguments.callee._prev_timeout_called = t;
var id = setTimeout(function () { d.call() }, 0);
d.canceller = function () { clearTimeout(id) };
}
if (fun) d.callback.ok = fun;
return d;
};
Deferred.next_faster_way_Image = ((typeof(Image) != "undefined") && document.addEventListener) && function (fun) {
// Modern Browsers
var d = new Deferred();
var img = new Image();
var handler = function () {
d.canceller();
d.call();
};
img.addEventListener("load", handler, false);
img.addEventListener("error", handler, false);
d.canceller = function () {
img.removeEventListener("load", handler, false);
img.removeEventListener("error", handler, false);
};
img.src = "data:,/ _ / X";
if (fun) d.callback.ok = fun;
return d;
};
Deferred.next = Deferred.next_faster_way_readystatechange ||
Deferred.next_faster_way_Image ||
Deferred.next_default;
/* function wait (sec) //=> Deferred
*
* `wait` returns deferred that will be called after `sec` elapsed
* with real elapsed time (msec)
*
* Sample:
* wait(1).next(function (elapsed) {
* log(elapsed); //=> may be 990-1100
* });
*/
Deferred.wait = function (n) {
var d = new Deferred(), t = new Date();
var id = setTimeout(function () {
d.call((new Date).getTime() - t.getTime());
}, n * 1000);
d.canceller = function () { clearTimeout(id) };
return d;
};
/* function call (fun [, args...]) //=> Deferred
*
* `call` function is for calling function asynchronous.
*
* Sample:
* // like tail recursion
* next(function () {
* function pow (x, n) {
* function _pow (n, r) {
* print([n, r]);
* if (n == 0) return r;
* return call(_pow, n - 1, x * r);
* }
* return call(_pow, n, 1);
* }
* return call(pow, 2, 10);
* }).
* next(function (r) {
* print([r, "end"]);
* });
*
*/
Deferred.call = function (f /* , args... */) {
var args = Array.prototype.slice.call(arguments, 1);
return Deferred.next(function () {
return f.apply(this, args);
});
};
/* function parallel (deferredlist) //=> Deferred
*
* `parallel` wraps up `deferredlist` to one deferred.
* This is useful when some asynchronous resources required.
*
* `deferredlist` can be Array or Object (Hash).
*
* Sample:
* parallel([
* $.get("foo.html"),
* $.get("bar.html")
* ]).next(function (values) {
* values[0] //=> foo.html data
* values[1] //=> bar.html data
* });
*
* parallel({
* foo: $.get("foo.html"),
* bar: $.get("bar.html")
* }).next(function (values) {
* values.foo //=> foo.html data
* values.bar //=> bar.html data
* });
*/
Deferred.parallel = function (dl) {
if (arguments.length > 1) dl = Array.prototype.slice.call(arguments);
var ret = new Deferred(), values = {}, num = 0;
for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
d.next(function (v) {
values[i] = v;
if (--num <= 0) {
if (dl instanceof Array) {
values.length = dl.length;
values = Array.prototype.slice.call(values, 0);
}
ret.call(values);
}
}).error(function (e) {
ret.fail(e);
});
num++;
})(dl[i], i);
if (!num) Deferred.next(function () { ret.call() });
ret.canceller = function () {
for (var i in dl) if (dl.hasOwnProperty(i)) {
dl[i].cancel();
}
};
return ret;
};
/* function earlier (deferredlist) //=> Deferred
*
* Continue process when one deferred in `deferredlist` has completed. Others will cancel.
* parallel ('and' processing) <=> earlier ('or' processing)
*/
Deferred.earlier = function (dl) {
var ret = new Deferred(), values = {}, num = 0;
for (var i in dl) if (dl.hasOwnProperty(i)) (function (d, i) {
d.next(function (v) {
values[i] = v;
if (dl instanceof Array) {
values.length = dl.length;
values = Array.prototype.slice.call(values, 0);
}
ret.canceller();
ret.call(values);
}).error(function (e) {
ret.fail(e);
});
num++;
})(dl[i], i);
if (!num) Deferred.next(function () { ret.call() });
ret.canceller = function () {
for (var i in dl) if (dl.hasOwnProperty(i)) {
dl[i].cancel();
}
};
return ret;
};
/* function loop (n, fun) //=> Deferred
*
* `loop` function provides browser-non-blocking loop.
* This loop is slow but not stop browser's appearance.
*
* Sample:
* //=> loop 1 to 100
* loop({begin:1, end:100, step:10}, function (n, o) {
* for (var i = 0; i < o.step; i++) {
* log(n+i);
* }
* });
*
* //=> loop 10 times
* loop(10, function (n) {
* log(n);
* });
*/
Deferred.loop = function (n, fun) {
var o = {
begin : n.begin || 0,
end : (typeof n.end == "number") ? n.end : n - 1,
step : n.step || 1,
last : false,
prev : null
};
var ret, step = o.step;
return Deferred.next(function () {
function _loop (i) {
if (i <= o.end) {
if ((i + step) > o.end) {
o.last = true;
o.step = o.end - i + 1;
}
o.prev = ret;
ret = fun.call(this, i, o);
if (ret instanceof Deferred) {
return ret.next(function (r) {
ret = r;
return Deferred.call(_loop, i + step);
});
} else {
return Deferred.call(_loop, i + step);
}
} else {
return ret;
}
}
return (o.begin <= o.end) ? Deferred.call(_loop, o.begin) : null;
});
};
/* function repeat (n, fun) //=> Deferred
*
* Loop `n` tiems with `fun`.
* This function automatically return control to browser, if loop time over 20msec.
* This is useful for huge loop not to block browser UI.
*
* Sample::
* repeat(10, function (i) {
* i //=> 0,1,2,3,4,5,6,7,8,9
* });
*/
Deferred.repeat = function (n, f) {
var i = 0, end = {}, ret = null;
return Deferred.next(function () {
var t = (new Date()).getTime();
divide: {
do {
if (i >= n) break divide;
ret = f(i++);
} while ((new Date()).getTime() - t < 20);
return Deferred.call(arguments.callee);
}
});
};
/* function Deferred.register (name, fun) //=> void 0
*
* Register `fun` to Deferred prototype for method chain.
*
* Sample::
* // Deferred.register("loop", loop);
*
* // Global Deferred function
* loop(10, function (n) {
* print(n);
* }).
* // Registered Deferred.prototype.loop
* loop(10, function (n) {
* print(n);
* });
*/
Deferred.register = function (name, fun) {
this.prototype[name] = function () {
var a = arguments;
return this.next(function () {
return fun.apply(this, a);
});
};
};
Deferred.register("loop", Deferred.loop);
Deferred.register("wait", Deferred.wait);
/* Deferred.connect (func [, opts: { ok : 0, ng : null, target: null} ]) //=> Function //=> Deferred
*
* Connect a function with Deferred. That is, transform a function
* that takes a callback into one that returns a Deferred object.
*
* Sample::
* var timeout = Deferred.connect(setTimeout, { target: window, ok: 0 });
* timeout(1).next(function () {
* alert('after 1 sec');
* });
*/
// Allow to pass multiple values to next.
Deferred.Arguments = function (args) { this.args = Array.prototype.slice.call(args, 0) }
Deferred.connect = function (func, obj) {
if (!obj) obj = {};
var callbackArgIndex = obj.ok;
var errorbackArgIndex = obj.ng;
var target = obj.target;
return function () {
var d = new Deferred();
d.next = function (fun) { return this._post("ok", function () {
fun.apply(this, (arguments[0] instanceof Deferred.Arguments) ? arguments[0].args : arguments);
}) };
var args = Array.prototype.slice.call(arguments, 0);
if (!(isFinite(callbackArgIndex) && callbackArgIndex !== null)) {
callbackArgIndex = args.length;
}
var callback = function () { d.call(new Deferred.Arguments(arguments)) };
args.splice(callbackArgIndex, 0, callback);
if (isFinite(errorbackArgIndex) && errorbackArgIndex !== null) {
var errorback = function () { d.fail(arguments) };
args.splice(errorbackArgIndex, 0, errorback);
}
Deferred.next(function () { func.apply(target, args) });
return d;
}
}
/* Deferred.retry(retryCount, func [, options = { wait : 0 } ])
*
* Try func (returns Deferred) max `retryCount`.
*
* Sample::
* Deferred.retry(3, function () {
* return http.get(...);
* }).
* next(function (res) {
* res //=> response if succeeded
* }).
* error(function (e) {
* e //=> error if all try failed
* });
*/
Deferred.retry = function (retryCount, funcDeferred/* funcDeferred() return Deferred */, options) {
if (!options) options = {};
var wait = options.wait || 0;
var d = new Deferred();
var retry = function () {
var m = funcDeferred(retryCount);
m.
next(function (mes) {
d.call(mes);
}).
error(function (e) {
if (--retryCount <= 0) {
d.fail(['retry failed', e]);
} else {
setTimeout(retry, wait * 1000);
}
});
};
setTimeout(retry, 0);
return d;
}
Deferred.define = function (obj, list) {
if (!list) list = ["parallel", "wait", "next", "call", "loop", "repeat"];
if (!obj) obj = (function getGlobal () { return this })();
for (var i = 0; i < list.length; i++) {
var n = list[i];
obj[n] = Deferred[n];
}
return Deferred;
};