testing/mochitest/MochiKit/Async.js

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:352f39cce3fb
1 /***
2
3 MochiKit.Async 1.4
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 if (typeof(dojo) != 'undefined') {
12 dojo.provide("MochiKit.Async");
13 dojo.require("MochiKit.Base");
14 }
15 if (typeof(JSAN) != 'undefined') {
16 JSAN.use("MochiKit.Base", []);
17 }
18
19 try {
20 if (typeof(MochiKit.Base) == 'undefined') {
21 throw "";
22 }
23 } catch (e) {
24 throw "MochiKit.Async depends on MochiKit.Base!";
25 }
26
27 if (typeof(MochiKit.Async) == 'undefined') {
28 MochiKit.Async = {};
29 }
30
31 MochiKit.Async.NAME = "MochiKit.Async";
32 MochiKit.Async.VERSION = "1.4";
33 MochiKit.Async.__repr__ = function () {
34 return "[" + this.NAME + " " + this.VERSION + "]";
35 };
36 MochiKit.Async.toString = function () {
37 return this.__repr__();
38 };
39
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;
50 };
51
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 },
65
66 toString: MochiKit.Base.forwardCall("repr"),
67
68 _nextId: MochiKit.Base.counter(),
69
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 },
86
87 _resback: function (res) {
88 /***
89
90 The primitive that means either callback or errback
91
92 ***/
93 this.fired = ((res instanceof Error) ? 1 : 0);
94 this.results[this.fired] = res;
95 this._fire();
96 },
97
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 },
107
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 },
116
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 },
129
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 },
137
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 },
145
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 },
153
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 },
165
166 _fire: function () {
167 /***
168
169 Used internally to exhaust the callback sequence when a result
170 is available.
171
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 }
215 };
216
217 MochiKit.Base.update(MochiKit.Async, {
218 /** @id MochiKit.Async.evalJSONRequest */
219 evalJSONRequest: function (/* req */) {
220 return eval('(' + arguments[0].responseText + ')');
221 },
222
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 },
229
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 },
236
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 },
262
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, 304 is NOT_MODIFIED
288 if (status == 200 || status == 304) { // OK
289 d.callback(this);
290 } else {
291 var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
292 if (err.number) {
293 // XXX: This seems to happen on page change
294 d.errback(err);
295 } else {
296 // XXX: this seems to happen when the server is unreachable
297 d.errback(err);
298 }
299 }
300 }
301 },
302
303 _xhr_canceller: function (req) {
304 // IE SUCKS
305 try {
306 req.onreadystatechange = null;
307 } catch (e) {
308 try {
309 req.onreadystatechange = MochiKit.Base.noop;
310 } catch (e) {
311 }
312 }
313 req.abort();
314 },
315
316
317 /** @id MochiKit.Async.sendXMLHttpRequest */
318 sendXMLHttpRequest: function (req, /* optional */ sendContent) {
319 if (typeof(sendContent) == "undefined" || sendContent === null) {
320 sendContent = "";
321 }
322
323 var m = MochiKit.Base;
324 var self = MochiKit.Async;
325 var d = new self.Deferred(m.partial(self._xhr_canceller, req));
326
327 try {
328 req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
329 req, d);
330 req.send(sendContent);
331 } catch (e) {
332 try {
333 req.onreadystatechange = null;
334 } catch (ignore) {
335 // pass
336 }
337 d.errback(e);
338 }
339
340 return d;
341
342 },
343
344 /** @id MochiKit.Async.doXHR */
345 doXHR: function (url, opts) {
346 var m = MochiKit.Base;
347 opts = m.update({
348 method: 'GET',
349 sendContent: ''
350 /*
351 queryString: undefined,
352 username: undefined,
353 password: undefined,
354 headers: undefined,
355 mimeType: undefined
356 */
357 }, opts);
358 var self = MochiKit.Async;
359 var req = self.getXMLHttpRequest();
360 if (opts.queryString) {
361 var qs = m.queryString(opts.queryString);
362 if (qs) {
363 url += "?" + qs;
364 }
365 }
366 req.open(opts.method, url, true, opts.username, opts.password);
367 if (req.overrideMimeType && opts.mimeType) {
368 req.overrideMimeType(opts.mimeType);
369 }
370 if (opts.headers) {
371 var headers = opts.headers;
372 if (!m.isArrayLike(headers)) {
373 headers = m.items(headers);
374 }
375 for (var i = 0; i < headers.length; i++) {
376 var header = headers[i];
377 var name = header[0];
378 var value = header[1];
379 req.setRequestHeader(name, value);
380 }
381 }
382 return self.sendXMLHttpRequest(req, opts.sendContent);
383 },
384
385 _buildURL: function (url/*, ...*/) {
386 if (arguments.length > 1) {
387 var m = MochiKit.Base;
388 var qs = m.queryString.apply(null, m.extend(null, arguments, 1));
389 if (qs) {
390 return url + "?" + qs;
391 }
392 }
393 return url;
394 },
395
396 /** @id MochiKit.Async.doSimpleXMLHttpRequest */
397 doSimpleXMLHttpRequest: function (url/*, ...*/) {
398 var self = MochiKit.Async;
399 url = self._buildURL.apply(self, arguments);
400 return self.doXHR(url);
401 },
402
403 /** @id MochiKit.Async.loadJSONDoc */
404 loadJSONDoc: function (url/*, ...*/) {
405 var self = MochiKit.Async;
406 url = self._buildURL.apply(self, arguments);
407 var d = self.doXHR(url, {
408 'mimeType': 'text/plain',
409 'headers': [['Accept', 'application/json']]
410 });
411 d = d.addCallback(self.evalJSONRequest);
412 return d;
413 },
414
415 /** @id MochiKit.Async.wait */
416 wait: function (seconds, /* optional */value) {
417 var d = new MochiKit.Async.Deferred();
418 var m = MochiKit.Base;
419 if (typeof(value) != 'undefined') {
420 d.addCallback(function () { return value; });
421 }
422 var timeout = setTimeout(
423 m.bind("callback", d),
424 Math.floor(seconds * 1000));
425 d.canceller = function () {
426 try {
427 clearTimeout(timeout);
428 } catch (e) {
429 // pass
430 }
431 };
432 return d;
433 },
434
435 /** @id MochiKit.Async.callLater */
436 callLater: function (seconds, func) {
437 var m = MochiKit.Base;
438 var pfunc = m.partial.apply(m, m.extend(null, arguments, 1));
439 return MochiKit.Async.wait(seconds).addCallback(
440 function (res) { return pfunc(); }
441 );
442 }
443 });
444
445
446 /** @id MochiKit.Async.DeferredLock */
447 MochiKit.Async.DeferredLock = function () {
448 this.waiting = [];
449 this.locked = false;
450 this.id = this._nextId();
451 };
452
453 MochiKit.Async.DeferredLock.prototype = {
454 __class__: MochiKit.Async.DeferredLock,
455 /** @id MochiKit.Async.DeferredLock.prototype.acquire */
456 acquire: function () {
457 var d = new MochiKit.Async.Deferred();
458 if (this.locked) {
459 this.waiting.push(d);
460 } else {
461 this.locked = true;
462 d.callback(this);
463 }
464 return d;
465 },
466 /** @id MochiKit.Async.DeferredLock.prototype.release */
467 release: function () {
468 if (!this.locked) {
469 throw TypeError("Tried to release an unlocked DeferredLock");
470 }
471 this.locked = false;
472 if (this.waiting.length > 0) {
473 this.locked = true;
474 this.waiting.shift().callback(this);
475 }
476 },
477 _nextId: MochiKit.Base.counter(),
478 repr: function () {
479 var state;
480 if (this.locked) {
481 state = 'locked, ' + this.waiting.length + ' waiting';
482 } else {
483 state = 'unlocked';
484 }
485 return 'DeferredLock(' + this.id + ', ' + state + ')';
486 },
487 toString: MochiKit.Base.forwardCall("repr")
488
489 };
490
491 /** @id MochiKit.Async.DeferredList */
492 MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) {
493
494 // call parent constructor
495 MochiKit.Async.Deferred.apply(this, [canceller]);
496
497 this.list = list;
498 var resultList = [];
499 this.resultList = resultList;
500
501 this.finishedCount = 0;
502 this.fireOnOneCallback = fireOnOneCallback;
503 this.fireOnOneErrback = fireOnOneErrback;
504 this.consumeErrors = consumeErrors;
505
506 var cb = MochiKit.Base.bind(this._cbDeferred, this);
507 for (var i = 0; i < list.length; i++) {
508 var d = list[i];
509 resultList.push(undefined);
510 d.addCallback(cb, i, true);
511 d.addErrback(cb, i, false);
512 }
513
514 if (list.length === 0 && !fireOnOneCallback) {
515 this.callback(this.resultList);
516 }
517
518 };
519
520 MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred();
521
522 MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) {
523 this.resultList[index] = [succeeded, result];
524 this.finishedCount += 1;
525 if (this.fired == -1) {
526 if (succeeded && this.fireOnOneCallback) {
527 this.callback([index, result]);
528 } else if (!succeeded && this.fireOnOneErrback) {
529 this.errback(result);
530 } else if (this.finishedCount == this.list.length) {
531 this.callback(this.resultList);
532 }
533 }
534 if (!succeeded && this.consumeErrors) {
535 result = null;
536 }
537 return result;
538 };
539
540 /** @id MochiKit.Async.gatherResults */
541 MochiKit.Async.gatherResults = function (deferredList) {
542 var d = new MochiKit.Async.DeferredList(deferredList, false, true, false);
543 d.addCallback(function (results) {
544 var ret = [];
545 for (var i = 0; i < results.length; i++) {
546 ret.push(results[i][1]);
547 }
548 return ret;
549 });
550 return d;
551 };
552
553 /** @id MochiKit.Async.maybeDeferred */
554 MochiKit.Async.maybeDeferred = function (func) {
555 var self = MochiKit.Async;
556 var result;
557 try {
558 var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
559 if (r instanceof self.Deferred) {
560 result = r;
561 } else if (r instanceof Error) {
562 result = self.fail(r);
563 } else {
564 result = self.succeed(r);
565 }
566 } catch (e) {
567 result = self.fail(e);
568 }
569 return result;
570 };
571
572
573 MochiKit.Async.EXPORT = [
574 "AlreadyCalledError",
575 "CancelledError",
576 "BrowserComplianceError",
577 "GenericError",
578 "XMLHttpRequestError",
579 "Deferred",
580 "succeed",
581 "fail",
582 "getXMLHttpRequest",
583 "doSimpleXMLHttpRequest",
584 "loadJSONDoc",
585 "wait",
586 "callLater",
587 "sendXMLHttpRequest",
588 "DeferredLock",
589 "DeferredList",
590 "gatherResults",
591 "maybeDeferred",
592 "doXHR"
593 ];
594
595 MochiKit.Async.EXPORT_OK = [
596 "evalJSONRequest"
597 ];
598
599 MochiKit.Async.__new__ = function () {
600 var m = MochiKit.Base;
601 var ne = m.partial(m._newNamedError, this);
602
603 ne("AlreadyCalledError",
604 /** @id MochiKit.Async.AlreadyCalledError */
605 function (deferred) {
606 /***
607
608 Raised by the Deferred if callback or errback happens
609 after it was already fired.
610
611 ***/
612 this.deferred = deferred;
613 }
614 );
615
616 ne("CancelledError",
617 /** @id MochiKit.Async.CancelledError */
618 function (deferred) {
619 /***
620
621 Raised by the Deferred cancellation mechanism.
622
623 ***/
624 this.deferred = deferred;
625 }
626 );
627
628 ne("BrowserComplianceError",
629 /** @id MochiKit.Async.BrowserComplianceError */
630 function (msg) {
631 /***
632
633 Raised when the JavaScript runtime is not capable of performing
634 the given function. Technically, this should really never be
635 raised because a non-conforming JavaScript runtime probably
636 isn't going to support exceptions in the first place.
637
638 ***/
639 this.message = msg;
640 }
641 );
642
643 ne("GenericError",
644 /** @id MochiKit.Async.GenericError */
645 function (msg) {
646 this.message = msg;
647 }
648 );
649
650 ne("XMLHttpRequestError",
651 /** @id MochiKit.Async.XMLHttpRequestError */
652 function (req, msg) {
653 /***
654
655 Raised when an XMLHttpRequest does not complete for any reason.
656
657 ***/
658 this.req = req;
659 this.message = msg;
660 try {
661 // Strange but true that this can raise in some cases.
662 this.number = req.status;
663 } catch (e) {
664 // pass
665 }
666 }
667 );
668
669
670 this.EXPORT_TAGS = {
671 ":common": this.EXPORT,
672 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
673 };
674
675 m.nameFunctions(this);
676
677 };
678
679 MochiKit.Async.__new__();
680
681 MochiKit.Base._exportSymbols(this, MochiKit.Async);

mercurial