c4bc6f56a8d669b9aff3f89a72743bd32d54a961
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
) {
349 Work around a Firefox bug by dealing with XHR during
350 the next event loop iteration. Maybe it's this one:
351 https://bugzilla.mozilla.org/show_bug.cgi?id=249843
353 var self
= MochiKit
.Async
;
354 return self
.callLater(0, self
._doXHR
, url
, opts
);
357 _doXHR
: function (url
, opts
) {
358 var m
= MochiKit
.Base
;
363 queryString: undefined,
370 var self
= MochiKit
.Async
;
371 var req
= self
.getXMLHttpRequest();
372 if (opts
.queryString
) {
373 var qs
= m
.queryString(opts
.queryString
);
378 // Safari will send undefined:undefined, so we have to check.
379 // We can't use apply, since the function is native.
380 if ('username' in opts
) {
381 req
.open(opts
.method
, url
, true, opts
.username
, opts
.password
);
383 req
.open(opts
.method
, url
, true);
385 if (req
.overrideMimeType
&& opts
.mimeType
) {
386 req
.overrideMimeType(opts
.mimeType
);
389 var headers
= opts
.headers
;
390 if (!m
.isArrayLike(headers
)) {
391 headers
= m
.items(headers
);
393 for (var i
= 0; i
< headers
.length
; i
++) {
394 var header
= headers
[i
];
395 var name
= header
[0];
396 var value
= header
[1];
397 req
.setRequestHeader(name
, value
);
400 return self
.sendXMLHttpRequest(req
, opts
.sendContent
);
403 _buildURL
: function (url
/*, ...*/) {
404 if (arguments
.length
> 1) {
405 var m
= MochiKit
.Base
;
406 var qs
= m
.queryString
.apply(null, m
.extend(null, arguments
, 1));
408 return url
+ "?" + qs
;
414 /** @id MochiKit.Async.doSimpleXMLHttpRequest */
415 doSimpleXMLHttpRequest
: function (url
/*, ...*/) {
416 var self
= MochiKit
.Async
;
417 url
= self
._buildURL
.apply(self
, arguments
);
418 return self
.doXHR(url
);
421 /** @id MochiKit.Async.loadJSONDoc */
422 loadJSONDoc
: function (url
/*, ...*/) {
423 var self
= MochiKit
.Async
;
424 url
= self
._buildURL
.apply(self
, arguments
);
425 var d
= self
.doXHR(url
, {
426 'mimeType': 'text/plain',
427 'headers': [['Accept', 'application/json']]
429 d
= d
.addCallback(self
.evalJSONRequest
);
433 /** @id MochiKit.Async.wait */
434 wait
: function (seconds
, /* optional */value
) {
435 var d
= new MochiKit
.Async
.Deferred();
436 var m
= MochiKit
.Base
;
437 if (typeof(value
) != 'undefined') {
438 d
.addCallback(function () { return value
; });
440 var timeout
= setTimeout(
441 m
.bind("callback", d
),
442 Math
.floor(seconds
* 1000));
443 d
.canceller
= function () {
445 clearTimeout(timeout
);
453 /** @id MochiKit.Async.callLater */
454 callLater
: function (seconds
, func
) {
455 var m
= MochiKit
.Base
;
456 var pfunc
= m
.partial
.apply(m
, m
.extend(null, arguments
, 1));
457 return MochiKit
.Async
.wait(seconds
).addCallback(
458 function (res
) { return pfunc(); }
464 /** @id MochiKit.Async.DeferredLock */
465 MochiKit
.Async
.DeferredLock
= function () {
468 this.id
= this._nextId();
471 MochiKit
.Async
.DeferredLock
.prototype = {
472 __class__
: MochiKit
.Async
.DeferredLock
,
473 /** @id MochiKit.Async.DeferredLock.prototype.acquire */
474 acquire
: function () {
475 var d
= new MochiKit
.Async
.Deferred();
477 this.waiting
.push(d
);
484 /** @id MochiKit.Async.DeferredLock.prototype.release */
485 release
: function () {
487 throw TypeError("Tried to release an unlocked DeferredLock");
490 if (this.waiting
.length
> 0) {
492 this.waiting
.shift().callback(this);
495 _nextId
: MochiKit
.Base
.counter(),
499 state
= 'locked, ' + this.waiting
.length
+ ' waiting';
503 return 'DeferredLock(' + this.id
+ ', ' + state
+ ')';
505 toString
: MochiKit
.Base
.forwardCall("repr")
509 /** @id MochiKit.Async.DeferredList */
510 MochiKit
.Async
.DeferredList
= function (list
, /* optional */fireOnOneCallback
, fireOnOneErrback
, consumeErrors
, canceller
) {
512 // call parent constructor
513 MochiKit
.Async
.Deferred
.apply(this, [canceller
]);
517 this.resultList
= resultList
;
519 this.finishedCount
= 0;
520 this.fireOnOneCallback
= fireOnOneCallback
;
521 this.fireOnOneErrback
= fireOnOneErrback
;
522 this.consumeErrors
= consumeErrors
;
524 var cb
= MochiKit
.Base
.bind(this._cbDeferred
, this);
525 for (var i
= 0; i
< list
.length
; i
++) {
527 resultList
.push(undefined
);
528 d
.addCallback(cb
, i
, true);
529 d
.addErrback(cb
, i
, false);
532 if (list
.length
=== 0 && !fireOnOneCallback
) {
533 this.callback(this.resultList
);
538 MochiKit
.Async
.DeferredList
.prototype = new MochiKit
.Async
.Deferred();
540 MochiKit
.Async
.DeferredList
.prototype._cbDeferred
= function (index
, succeeded
, result
) {
541 this.resultList
[index
] = [succeeded
, result
];
542 this.finishedCount
+= 1;
543 if (this.fired
== -1) {
544 if (succeeded
&& this.fireOnOneCallback
) {
545 this.callback([index
, result
]);
546 } else if (!succeeded
&& this.fireOnOneErrback
) {
547 this.errback(result
);
548 } else if (this.finishedCount
== this.list
.length
) {
549 this.callback(this.resultList
);
552 if (!succeeded
&& this.consumeErrors
) {
558 /** @id MochiKit.Async.gatherResults */
559 MochiKit
.Async
.gatherResults
= function (deferredList
) {
560 var d
= new MochiKit
.Async
.DeferredList(deferredList
, false, true, false);
561 d
.addCallback(function (results
) {
563 for (var i
= 0; i
< results
.length
; i
++) {
564 ret
.push(results
[i
][1]);
571 /** @id MochiKit.Async.maybeDeferred */
572 MochiKit
.Async
.maybeDeferred
= function (func
) {
573 var self
= MochiKit
.Async
;
576 var r
= func
.apply(null, MochiKit
.Base
.extend([], arguments
, 1));
577 if (r
instanceof self
.Deferred
) {
579 } else if (r
instanceof Error
) {
580 result
= self
.fail(r
);
582 result
= self
.succeed(r
);
585 result
= self
.fail(e
);
591 MochiKit
.Async
.EXPORT
= [
592 "AlreadyCalledError",
594 "BrowserComplianceError",
596 "XMLHttpRequestError",
601 "doSimpleXMLHttpRequest",
605 "sendXMLHttpRequest",
613 MochiKit
.Async
.EXPORT_OK
= [
617 MochiKit
.Async
.__new__
= function () {
618 var m
= MochiKit
.Base
;
619 var ne
= m
.partial(m
._newNamedError
, this);
621 ne("AlreadyCalledError",
622 /** @id MochiKit.Async.AlreadyCalledError */
623 function (deferred
) {
626 Raised by the Deferred if callback or errback happens
627 after it was already fired.
630 this.deferred
= deferred
;
635 /** @id MochiKit.Async.CancelledError */
636 function (deferred
) {
639 Raised by the Deferred cancellation mechanism.
642 this.deferred
= deferred
;
646 ne("BrowserComplianceError",
647 /** @id MochiKit.Async.BrowserComplianceError */
651 Raised when the JavaScript runtime is not capable of performing
652 the given function. Technically, this should really never be
653 raised because a non-conforming JavaScript runtime probably
654 isn't going to support exceptions in the first place.
662 /** @id MochiKit.Async.GenericError */
668 ne("XMLHttpRequestError",
669 /** @id MochiKit.Async.XMLHttpRequestError */
670 function (req
, msg
) {
673 Raised when an XMLHttpRequest does not complete for any reason.
679 // Strange but true that this can raise in some cases.
680 this.number
= req
.status
;
689 ":common": this.EXPORT
,
690 ":all": m
.concat(this.EXPORT
, this.EXPORT_OK
)
693 m
.nameFunctions(this);
697 MochiKit
.Async
.__new__();
699 MochiKit
.Base
._exportSymbols(this, MochiKit
.Async
);