5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2005 Bob Ippolito. All rights Reserved.
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide("MochiKit.Async");
13 dojo.require("MochiKit.Base");
15 if (typeof(JSAN) != 'undefined') {
16 JSAN.use("MochiKit.Base", []);
20 if (typeof(MochiKit.Base) == 'undefined') {
24 throw "MochiKit.Async depends on MochiKit.Base!";
27 if (typeof(MochiKit.Async) == 'undefined') {
31 MochiKit.Async.NAME = "MochiKit.Async";
32 MochiKit.Async.VERSION = "1.4";
33 MochiKit.Async.__repr__ = function () {
34 return "[" + this.NAME + " " + this.VERSION + "]";
36 MochiKit.Async.toString = function () {
37 return this.__repr__();
40 /** @id MochiKit.Async.Deferred */
41 MochiKit.Async.Deferred = function (/* optional */ canceller) {
43 this.id = this._nextId();
46 this.results = [null, null];
47 this.canceller = canceller;
48 this.silentlyCancelled = false;
52 MochiKit.Async.Deferred.prototype = {
53 /** @id MochiKit.Async.Deferred.prototype.repr */
56 if (this.fired == -1) {
58 } else if (this.fired === 0) {
63 return 'Deferred(' + this.id + ', ' + state + ')';
66 toString: MochiKit.Base.forwardCall("repr"),
68 _nextId: MochiKit.Base.counter(),
70 /** @id MochiKit.Async.Deferred.prototype.cancel */
72 var self = MochiKit.Async;
73 if (this.fired == -1) {
77 this.silentlyCancelled = true;
79 if (this.fired == -1) {
80 this.errback(new self.CancelledError(this));
82 } else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
83 this.results[0].cancel();
87 _resback: function (res) {
90 The primitive that means either callback or errback
93 this.fired = ((res instanceof Error) ? 1 : 0);
94 this.results[this.fired] = res;
99 if (this.fired != -1) {
100 if (!this.silentlyCancelled) {
101 throw new MochiKit.Async.AlreadyCalledError(this);
103 this.silentlyCancelled = false;
108 /** @id MochiKit.Async.Deferred.prototype.callback */
109 callback: function (res) {
111 if (res instanceof MochiKit.Async.Deferred) {
112 throw new Error("Deferred instances can only be chained if they are the result of a callback");
117 /** @id MochiKit.Async.Deferred.prototype.errback */
118 errback: function (res) {
120 var self = MochiKit.Async;
121 if (res instanceof self.Deferred) {
122 throw new Error("Deferred instances can only be chained if they are the result of a callback");
124 if (!(res instanceof Error)) {
125 res = new self.GenericError(res);
130 /** @id MochiKit.Async.Deferred.prototype.addBoth */
131 addBoth: function (fn) {
132 if (arguments.length > 1) {
133 fn = MochiKit.Base.partial.apply(null, arguments);
135 return this.addCallbacks(fn, fn);
138 /** @id MochiKit.Async.Deferred.prototype.addCallback */
139 addCallback: function (fn) {
140 if (arguments.length > 1) {
141 fn = MochiKit.Base.partial.apply(null, arguments);
143 return this.addCallbacks(fn, null);
146 /** @id MochiKit.Async.Deferred.prototype.addErrback */
147 addErrback: function (fn) {
148 if (arguments.length > 1) {
149 fn = MochiKit.Base.partial.apply(null, arguments);
151 return this.addCallbacks(null, fn);
154 /** @id MochiKit.Async.Deferred.prototype.addCallbacks */
155 addCallbacks: function (cb, eb) {
157 throw new Error("Chained Deferreds can not be re-used");
159 this.chain.push([cb, eb]);
160 if (this.fired >= 0) {
169 Used internally to exhaust the callback sequence when a result
173 var chain = this.chain;
174 var fired = this.fired;
175 var res = this.results[fired];
178 while (chain.length > 0 && this.paused === 0) {
180 var pair = chain.shift();
187 fired = ((res instanceof Error) ? 1 : 0);
188 if (res instanceof MochiKit.Async.Deferred) {
189 cb = function (res) {
192 if ((self.paused === 0) && (self.fired >= 0)) {
200 if (!(err instanceof Error)) {
201 err = new MochiKit.Async.GenericError(err);
207 this.results[fired] = res;
208 if (cb && this.paused) {
209 // this is for "tail recursion" in case the dependent deferred
217 MochiKit.Base.update(MochiKit.Async, {
218 /** @id MochiKit.Async.evalJSONRequest */
219 evalJSONRequest: function (/* req */) {
220 return eval('(' + arguments[0].responseText + ')');
223 /** @id MochiKit.Async.succeed */
224 succeed: function (/* optional */result) {
225 var d = new MochiKit.Async.Deferred();
226 d.callback.apply(d, arguments);
230 /** @id MochiKit.Async.fail */
231 fail: function (/* optional */result) {
232 var d = new MochiKit.Async.Deferred();
233 d.errback.apply(d, arguments);
237 /** @id MochiKit.Async.getXMLHttpRequest */
238 getXMLHttpRequest: function () {
239 var self = arguments.callee;
240 if (!self.XMLHttpRequest) {
242 function () { return new XMLHttpRequest(); },
243 function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
244 function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
245 function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
247 throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
250 for (var i = 0; i < tryThese.length; i++) {
251 var func = tryThese[i];
253 self.XMLHttpRequest = func;
260 return self.XMLHttpRequest();
263 _xhr_onreadystatechange: function (d) {
264 // MochiKit.Logging.logDebug('this.readyState', this.readyState);
265 var m = MochiKit.Base;
266 if (this.readyState == 4) {
269 this.onreadystatechange = null;
272 this.onreadystatechange = m.noop;
278 status = this.status;
279 if (!status && m.isNotEmpty(this.responseText)) {
280 // 0 or undefined seems to mean cached or local
285 // MochiKit.Logging.logDebug('error getting status?', repr(items(e)));
287 // 200 is OK, 201 is CREATED, 204 is NO CONTENT
288 // 304 is NOT MODIFIED, 1223 is apparently a bug in IE
289 if (status == 200 || status == 201 || status == 204 ||
290 status == 304 || status == 1223) {
293 var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
295 // XXX: This seems to happen on page change
298 // XXX: this seems to happen when the server is unreachable
305 _xhr_canceller: function (req) {
308 req.onreadystatechange = null;
311 req.onreadystatechange = MochiKit.Base.noop;
319 /** @id MochiKit.Async.sendXMLHttpRequest */
320 sendXMLHttpRequest: function (req, /* optional */ sendContent) {
321 if (typeof(sendContent) == "undefined" || sendContent === null) {
325 var m = MochiKit.Base;
326 var self = MochiKit.Async;
327 var d = new self.Deferred(m.partial(self._xhr_canceller, req));
330 req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
332 req.send(sendContent);
335 req.onreadystatechange = null;
346 /** @id MochiKit.Async.doXHR */
347 doXHR: function (url, opts) {
348 var m = MochiKit.Base;
353 queryString: undefined,
360 var self = MochiKit.Async;
361 var req = self.getXMLHttpRequest();
362 if (opts.queryString) {
363 var qs = m.queryString(opts.queryString);
368 // Safari will send undefined:undefined, so we have to check.
369 // We can't use apply, since the function is native.
370 if ('username' in opts) {
371 req.open(opts.method, url, true, opts.username, opts.password);
373 req.open(opts.method, url, true);
375 if (req.overrideMimeType && opts.mimeType) {
376 req.overrideMimeType(opts.mimeType);
379 var headers = opts.headers;
380 if (!m.isArrayLike(headers)) {
381 headers = m.items(headers);
383 for (var i = 0; i < headers.length; i++) {
384 var header = headers[i];
385 var name = header[0];
386 var value = header[1];
387 req.setRequestHeader(name, value);
390 return self.sendXMLHttpRequest(req, opts.sendContent);
393 _buildURL: function (url/*, ...*/) {
394 if (arguments.length > 1) {
395 var m = MochiKit.Base;
396 var qs = m.queryString.apply(null, m.extend(null, arguments, 1));
398 return url + "?" + qs;
404 /** @id MochiKit.Async.doSimpleXMLHttpRequest */
405 doSimpleXMLHttpRequest: function (url/*, ...*/) {
406 var self = MochiKit.Async;
407 url = self._buildURL.apply(self, arguments);
408 return self.doXHR(url);
411 /** @id MochiKit.Async.loadJSONDoc */
412 loadJSONDoc: function (url/*, ...*/) {
413 var self = MochiKit.Async;
414 url = self._buildURL.apply(self, arguments);
415 var d = self.doXHR(url, {
416 'mimeType': 'text/plain',
417 'headers': [['Accept', 'application/json']]
419 d = d.addCallback(self.evalJSONRequest);
423 /** @id MochiKit.Async.wait */
424 wait: function (seconds, /* optional */value) {
425 var d = new MochiKit.Async.Deferred();
426 var m = MochiKit.Base;
427 if (typeof(value) != 'undefined') {
428 d.addCallback(function () { return value; });
430 var timeout = setTimeout(
431 m.bind("callback", d),
432 Math.floor(seconds * 1000));
433 d.canceller = function () {
435 clearTimeout(timeout);
443 /** @id MochiKit.Async.callLater */
444 callLater: function (seconds, func) {
445 var m = MochiKit.Base;
446 var pfunc = m.partial.apply(m, m.extend(null, arguments, 1));
447 return MochiKit.Async.wait(seconds).addCallback(
448 function (res) { return pfunc(); }
454 /** @id MochiKit.Async.DeferredLock */
455 MochiKit.Async.DeferredLock = function () {
458 this.id = this._nextId();
461 MochiKit.Async.DeferredLock.prototype = {
462 __class__: MochiKit.Async.DeferredLock,
463 /** @id MochiKit.Async.DeferredLock.prototype.acquire */
464 acquire: function () {
465 var d = new MochiKit.Async.Deferred();
467 this.waiting.push(d);
474 /** @id MochiKit.Async.DeferredLock.prototype.release */
475 release: function () {
477 throw TypeError("Tried to release an unlocked DeferredLock");
480 if (this.waiting.length > 0) {
482 this.waiting.shift().callback(this);
485 _nextId: MochiKit.Base.counter(),
489 state = 'locked, ' + this.waiting.length + ' waiting';
493 return 'DeferredLock(' + this.id + ', ' + state + ')';
495 toString: MochiKit.Base.forwardCall("repr")
499 /** @id MochiKit.Async.DeferredList */
500 MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) {
502 // call parent constructor
503 MochiKit.Async.Deferred.apply(this, [canceller]);
507 this.resultList = resultList;
509 this.finishedCount = 0;
510 this.fireOnOneCallback = fireOnOneCallback;
511 this.fireOnOneErrback = fireOnOneErrback;
512 this.consumeErrors = consumeErrors;
514 var cb = MochiKit.Base.bind(this._cbDeferred, this);
515 for (var i = 0; i < list.length; i++) {
517 resultList.push(undefined);
518 d.addCallback(cb, i, true);
519 d.addErrback(cb, i, false);
522 if (list.length === 0 && !fireOnOneCallback) {
523 this.callback(this.resultList);
528 MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred();
530 MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) {
531 this.resultList[index] = [succeeded, result];
532 this.finishedCount += 1;
533 if (this.fired == -1) {
534 if (succeeded && this.fireOnOneCallback) {
535 this.callback([index, result]);
536 } else if (!succeeded && this.fireOnOneErrback) {
537 this.errback(result);
538 } else if (this.finishedCount == this.list.length) {
539 this.callback(this.resultList);
542 if (!succeeded && this.consumeErrors) {
548 /** @id MochiKit.Async.gatherResults */
549 MochiKit.Async.gatherResults = function (deferredList) {
550 var d = new MochiKit.Async.DeferredList(deferredList, false, true, false);
551 d.addCallback(function (results) {
553 for (var i = 0; i < results.length; i++) {
554 ret.push(results[i][1]);
561 /** @id MochiKit.Async.maybeDeferred */
562 MochiKit.Async.maybeDeferred = function (func) {
563 var self = MochiKit.Async;
566 var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
567 if (r instanceof self.Deferred) {
569 } else if (r instanceof Error) {
570 result = self.fail(r);
572 result = self.succeed(r);
575 result = self.fail(e);
581 MochiKit.Async.EXPORT = [
582 "AlreadyCalledError",
584 "BrowserComplianceError",
586 "XMLHttpRequestError",
591 "doSimpleXMLHttpRequest",
595 "sendXMLHttpRequest",
603 MochiKit.Async.EXPORT_OK = [
607 MochiKit.Async.__new__ = function () {
608 var m = MochiKit.Base;
609 var ne = m.partial(m._newNamedError, this);
611 ne("AlreadyCalledError",
612 /** @id MochiKit.Async.AlreadyCalledError */
613 function (deferred) {
616 Raised by the Deferred if callback or errback happens
617 after it was already fired.
620 this.deferred = deferred;
625 /** @id MochiKit.Async.CancelledError */
626 function (deferred) {
629 Raised by the Deferred cancellation mechanism.
632 this.deferred = deferred;
636 ne("BrowserComplianceError",
637 /** @id MochiKit.Async.BrowserComplianceError */
641 Raised when the JavaScript runtime is not capable of performing
642 the given function. Technically, this should really never be
643 raised because a non-conforming JavaScript runtime probably
644 isn't going to support exceptions in the first place.
652 /** @id MochiKit.Async.GenericError */
658 ne("XMLHttpRequestError",
659 /** @id MochiKit.Async.XMLHttpRequestError */
660 function (req, msg) {
663 Raised when an XMLHttpRequest does not complete for any reason.
669 // Strange but true that this can raise in some cases.
670 this.number = req.status;
679 ":common": this.EXPORT,
680 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
683 m.nameFunctions(this);
687 MochiKit.Async.__new__();
689 MochiKit.Base._exportSymbols(this, MochiKit.Async);