Make Build.PL less complainy and add manifest
[cxgn-jslib.git] / MochiKit / Async.js
blob42c23149cd6d275710fdbf15128220840ced2ed8
1 /***
3 MochiKit.Async 1.4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
7 (c) 2005 Bob Ippolito.  All rights Reserved.
9 ***/
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", []);
19 try {
20     if (typeof(MochiKit.Base) == 'undefined') {
21         throw "";
22     }
23 } catch (e) {
24     throw "MochiKit.Async depends on MochiKit.Base!";
27 if (typeof(MochiKit.Async) == 'undefined') {
28     MochiKit.Async = {};
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) {
42     this.chain = [];
43     this.id = this._nextId();
44     this.fired = -1;
45     this.paused = 0;
46     this.results = [null, null];
47     this.canceller = canceller;
48     this.silentlyCancelled = false;
49     this.chained = false;
52 MochiKit.Async.Deferred.prototype = {
53     /** @id MochiKit.Async.Deferred.prototype.repr */
54     repr: function () {
55         var state;
56         if (this.fired == -1) {
57             state = 'unfired';
58         } else if (this.fired === 0) {
59             state = 'success';
60         } else {
61             state = 'error';
62         }
63         return 'Deferred(' + this.id + ', ' + state + ')';
64     },
66     toString: MochiKit.Base.forwardCall("repr"),
68     _nextId: MochiKit.Base.counter(),
70     /** @id MochiKit.Async.Deferred.prototype.cancel */
71     cancel: function () {
72         var self = MochiKit.Async;
73         if (this.fired == -1) {
74             if (this.canceller) {
75                 this.canceller(this);
76             } else {
77                 this.silentlyCancelled = true;
78             }
79             if (this.fired == -1) {
80                 this.errback(new self.CancelledError(this));
81             }
82         } else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
83             this.results[0].cancel();
84         }
85     },
87     _resback: function (res) {
88         /***
90         The primitive that means either callback or errback
92         ***/
93         this.fired = ((res instanceof Error) ? 1 : 0);
94         this.results[this.fired] = res;
95         this._fire();
96     },
98     _check: function () {
99         if (this.fired != -1) {
100             if (!this.silentlyCancelled) {
101                 throw new MochiKit.Async.AlreadyCalledError(this);
102             }
103             this.silentlyCancelled = false;
104             return;
105         }
106     },
108     /** @id MochiKit.Async.Deferred.prototype.callback */
109     callback: function (res) {
110         this._check();
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");
113         }
114         this._resback(res);
115     },
117     /** @id MochiKit.Async.Deferred.prototype.errback */
118     errback: function (res) {
119         this._check();
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");
123         }
124         if (!(res instanceof Error)) {
125             res = new self.GenericError(res);
126         }
127         this._resback(res);
128     },
130     /** @id MochiKit.Async.Deferred.prototype.addBoth */
131     addBoth: function (fn) {
132         if (arguments.length > 1) {
133             fn = MochiKit.Base.partial.apply(null, arguments);
134         }
135         return this.addCallbacks(fn, fn);
136     },
138     /** @id MochiKit.Async.Deferred.prototype.addCallback */
139     addCallback: function (fn) {
140         if (arguments.length > 1) {
141             fn = MochiKit.Base.partial.apply(null, arguments);
142         }
143         return this.addCallbacks(fn, null);
144     },
146     /** @id MochiKit.Async.Deferred.prototype.addErrback */
147     addErrback: function (fn) {
148         if (arguments.length > 1) {
149             fn = MochiKit.Base.partial.apply(null, arguments);
150         }
151         return this.addCallbacks(null, fn);
152     },
154     /** @id MochiKit.Async.Deferred.prototype.addCallbacks */
155     addCallbacks: function (cb, eb) {
156         if (this.chained) {
157             throw new Error("Chained Deferreds can not be re-used");
158         }
159         this.chain.push([cb, eb]);
160         if (this.fired >= 0) {
161             this._fire();
162         }
163         return this;
164     },
166     _fire: function () {
167         /***
169         Used internally to exhaust the callback sequence when a result
170         is available.
172         ***/
173         var chain = this.chain;
174         var fired = this.fired;
175         var res = this.results[fired];
176         var self = this;
177         var cb = null;
178         while (chain.length > 0 && this.paused === 0) {
179             // Array
180             var pair = chain.shift();
181             var f = pair[fired];
182             if (f === null) {
183                 continue;
184             }
185             try {
186                 res = f(res);
187                 fired = ((res instanceof Error) ? 1 : 0);
188                 if (res instanceof MochiKit.Async.Deferred) {
189                     cb = function (res) {
190                         self._resback(res);
191                         self.paused--;
192                         if ((self.paused === 0) && (self.fired >= 0)) {
193                             self._fire();
194                         }
195                     };
196                     this.paused++;
197                 }
198             } catch (err) {
199                 fired = 1;
200                 if (!(err instanceof Error)) {
201                     err = new MochiKit.Async.GenericError(err);
202                 }
203                 res = err;
204             }
205         }
206         this.fired = fired;
207         this.results[fired] = res;
208         if (cb && this.paused) {
209             // this is for "tail recursion" in case the dependent deferred
210             // is already fired
211             res.addBoth(cb);
212             res.chained = true;
213         }
214     }
217 MochiKit.Base.update(MochiKit.Async, {
218     /** @id MochiKit.Async.evalJSONRequest */
219     evalJSONRequest: function (/* req */) {
220         return eval('(' + arguments[0].responseText + ')');
221     },
223     /** @id MochiKit.Async.succeed */
224     succeed: function (/* optional */result) {
225         var d = new MochiKit.Async.Deferred();
226         d.callback.apply(d, arguments);
227         return d;
228     },
230     /** @id MochiKit.Async.fail */
231     fail: function (/* optional */result) {
232         var d = new MochiKit.Async.Deferred();
233         d.errback.apply(d, arguments);
234         return d;
235     },
237     /** @id MochiKit.Async.getXMLHttpRequest */
238     getXMLHttpRequest: function () {
239         var self = arguments.callee;
240         if (!self.XMLHttpRequest) {
241             var tryThese = [
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'); },
246                 function () {
247                     throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
248                 }
249             ];
250             for (var i = 0; i < tryThese.length; i++) {
251                 var func = tryThese[i];
252                 try {
253                     self.XMLHttpRequest = func;
254                     return func();
255                 } catch (e) {
256                     // pass
257                 }
258             }
259         }
260         return self.XMLHttpRequest();
261     },
263     _xhr_onreadystatechange: function (d) {
264         // MochiKit.Logging.logDebug('this.readyState', this.readyState);
265         var m = MochiKit.Base;
266         if (this.readyState == 4) {
267             // IE SUCKS
268             try {
269                 this.onreadystatechange = null;
270             } catch (e) {
271                 try {
272                     this.onreadystatechange = m.noop;
273                 } catch (e) {
274                 }
275             }
276             var status = null;
277             try {
278                 status = this.status;
279                 if (!status && m.isNotEmpty(this.responseText)) {
280                     // 0 or undefined seems to mean cached or local
281                     status = 304;
282                 }
283             } catch (e) {
284                 // pass
285                 // MochiKit.Logging.logDebug('error getting status?', repr(items(e)));
286             }
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) {
291                 d.callback(this);
292             } else {
293                 var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
294                 if (err.number) {
295                     // XXX: This seems to happen on page change
296                     d.errback(err);
297                 } else {
298                     // XXX: this seems to happen when the server is unreachable
299                     d.errback(err);
300                 }
301             }
302         }
303     },
305     _xhr_canceller: function (req) {
306         // IE SUCKS
307         try {
308             req.onreadystatechange = null;
309         } catch (e) {
310             try {
311                 req.onreadystatechange = MochiKit.Base.noop;
312             } catch (e) {
313             }
314         }
315         req.abort();
316     },
319     /** @id MochiKit.Async.sendXMLHttpRequest */
320     sendXMLHttpRequest: function (req, /* optional */ sendContent) {
321         if (typeof(sendContent) == "undefined" || sendContent === null) {
322             sendContent = "";
323         }
325         var m = MochiKit.Base;
326         var self = MochiKit.Async;
327         var d = new self.Deferred(m.partial(self._xhr_canceller, req));
329         try {
330             req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
331                 req, d);
332             req.send(sendContent);
333         } catch (e) {
334             try {
335                 req.onreadystatechange = null;
336             } catch (ignore) {
337                 // pass
338             }
339             d.errback(e);
340         }
342         return d;
344     },
346     /** @id MochiKit.Async.doXHR */
347     doXHR: function (url, opts) {
348         var m = MochiKit.Base;
349         opts = m.update({
350             method: 'GET',
351             sendContent: ''
352             /*
353             queryString: undefined,
354             username: undefined,
355             password: undefined,
356             headers: undefined,
357             mimeType: undefined
358             */
359         }, opts);
360         var self = MochiKit.Async;
361         var req = self.getXMLHttpRequest();
362         if (opts.queryString) {
363             var qs = m.queryString(opts.queryString);
364             if (qs) {
365                 url += "?" + qs;
366             }
367         }
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);
372         } else {
373             req.open(opts.method, url, true);
374         }
375         if (req.overrideMimeType && opts.mimeType) {
376             req.overrideMimeType(opts.mimeType);
377         }
378         if (opts.headers) {
379             var headers = opts.headers;
380             if (!m.isArrayLike(headers)) {
381                 headers = m.items(headers);
382             }
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);
388             }
389         }
390         return self.sendXMLHttpRequest(req, opts.sendContent);
391     },
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));
397             if (qs) {
398                 return url + "?" + qs;
399             }
400         }
401         return url;
402     },
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);
409     },
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']]
418         });
419         d = d.addCallback(self.evalJSONRequest);
420         return d;
421     },
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; });
429         }
430         var timeout = setTimeout(
431             m.bind("callback", d),
432             Math.floor(seconds * 1000));
433         d.canceller = function () {
434             try {
435                 clearTimeout(timeout);
436             } catch (e) {
437                 // pass
438             }
439         };
440         return d;
441     },
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(); }
449         );
450     }
454 /** @id MochiKit.Async.DeferredLock */
455 MochiKit.Async.DeferredLock = function () {
456     this.waiting = [];
457     this.locked = false;
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();
466         if (this.locked) {
467             this.waiting.push(d);
468         } else {
469             this.locked = true;
470             d.callback(this);
471         }
472         return d;
473     },
474     /** @id MochiKit.Async.DeferredLock.prototype.release */
475     release: function () {
476         if (!this.locked) {
477             throw TypeError("Tried to release an unlocked DeferredLock");
478         }
479         this.locked = false;
480         if (this.waiting.length > 0) {
481             this.locked = true;
482             this.waiting.shift().callback(this);
483         }
484     },
485     _nextId: MochiKit.Base.counter(),
486     repr: function () {
487         var state;
488         if (this.locked) {
489             state = 'locked, ' + this.waiting.length + ' waiting';
490         } else {
491             state = 'unlocked';
492         }
493         return 'DeferredLock(' + this.id + ', ' + state + ')';
494     },
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]);
505     this.list = list;
506     var resultList = [];
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++) {
516         var d = list[i];
517         resultList.push(undefined);
518         d.addCallback(cb, i, true);
519         d.addErrback(cb, i, false);
520     }
522     if (list.length === 0 && !fireOnOneCallback) {
523         this.callback(this.resultList);
524     }
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);
540         }
541     }
542     if (!succeeded && this.consumeErrors) {
543         result = null;
544     }
545     return result;
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) {
552         var ret = [];
553         for (var i = 0; i < results.length; i++) {
554             ret.push(results[i][1]);
555         }
556         return ret;
557     });
558     return d;
561 /** @id MochiKit.Async.maybeDeferred */
562 MochiKit.Async.maybeDeferred = function (func) {
563     var self = MochiKit.Async;
564     var result;
565     try {
566         var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
567         if (r instanceof self.Deferred) {
568             result = r;
569         } else if (r instanceof Error) {
570             result = self.fail(r);
571         } else {
572             result = self.succeed(r);
573         }
574     } catch (e) {
575         result = self.fail(e);
576     }
577     return result;
581 MochiKit.Async.EXPORT = [
582     "AlreadyCalledError",
583     "CancelledError",
584     "BrowserComplianceError",
585     "GenericError",
586     "XMLHttpRequestError",
587     "Deferred",
588     "succeed",
589     "fail",
590     "getXMLHttpRequest",
591     "doSimpleXMLHttpRequest",
592     "loadJSONDoc",
593     "wait",
594     "callLater",
595     "sendXMLHttpRequest",
596     "DeferredLock",
597     "DeferredList",
598     "gatherResults",
599     "maybeDeferred",
600     "doXHR"
603 MochiKit.Async.EXPORT_OK = [
604     "evalJSONRequest"
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) {
614             /***
616             Raised by the Deferred if callback or errback happens
617             after it was already fired.
619             ***/
620             this.deferred = deferred;
621         }
622     );
624     ne("CancelledError",
625         /** @id MochiKit.Async.CancelledError */
626         function (deferred) {
627             /***
629             Raised by the Deferred cancellation mechanism.
631             ***/
632             this.deferred = deferred;
633         }
634     );
636     ne("BrowserComplianceError",
637         /** @id MochiKit.Async.BrowserComplianceError */
638         function (msg) {
639             /***
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.
646             ***/
647             this.message = msg;
648         }
649     );
651     ne("GenericError",
652         /** @id MochiKit.Async.GenericError */
653         function (msg) {
654             this.message = msg;
655         }
656     );
658     ne("XMLHttpRequestError",
659         /** @id MochiKit.Async.XMLHttpRequestError */
660         function (req, msg) {
661             /***
663             Raised when an XMLHttpRequest does not complete for any reason.
665             ***/
666             this.req = req;
667             this.message = msg;
668             try {
669                 // Strange but true that this can raise in some cases.
670                 this.number = req.status;
671             } catch (e) {
672                 // pass
673             }
674         }
675     );
678     this.EXPORT_TAGS = {
679         ":common": this.EXPORT,
680         ":all": m.concat(this.EXPORT, this.EXPORT_OK)
681     };
683     m.nameFunctions(this);
687 MochiKit.Async.__new__();
689 MochiKit.Base._exportSymbols(this, MochiKit.Async);