testing/mochitest/tests/MochiKit-1.4.2/MochiKit/Async.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:5deb0e027188
1 /***
2
3 MochiKit.Async 1.4.2
4
5 See <http://mochikit.com/> for documentation, downloads, license, etc.
6
7 (c) 2005 Bob Ippolito. All rights Reserved.
8
9 ***/
10
11 MochiKit.Base._deps('Async', ['Base']);
12
13 MochiKit.Async.NAME = "MochiKit.Async";
14 MochiKit.Async.VERSION = "1.4.2";
15 MochiKit.Async.__repr__ = function () {
16 return "[" + this.NAME + " " + this.VERSION + "]";
17 };
18 MochiKit.Async.toString = function () {
19 return this.__repr__();
20 };
21
22 /** @id MochiKit.Async.Deferred */
23 MochiKit.Async.Deferred = function (/* optional */ canceller) {
24 this.chain = [];
25 this.id = this._nextId();
26 this.fired = -1;
27 this.paused = 0;
28 this.results = [null, null];
29 this.canceller = canceller;
30 this.silentlyCancelled = false;
31 this.chained = false;
32 };
33
34 MochiKit.Async.Deferred.prototype = {
35 /** @id MochiKit.Async.Deferred.prototype.repr */
36 repr: function () {
37 var state;
38 if (this.fired == -1) {
39 state = 'unfired';
40 } else if (this.fired === 0) {
41 state = 'success';
42 } else {
43 state = 'error';
44 }
45 return 'Deferred(' + this.id + ', ' + state + ')';
46 },
47
48 toString: MochiKit.Base.forwardCall("repr"),
49
50 _nextId: MochiKit.Base.counter(),
51
52 /** @id MochiKit.Async.Deferred.prototype.cancel */
53 cancel: function () {
54 var self = MochiKit.Async;
55 if (this.fired == -1) {
56 if (this.canceller) {
57 this.canceller(this);
58 } else {
59 this.silentlyCancelled = true;
60 }
61 if (this.fired == -1) {
62 this.errback(new self.CancelledError(this));
63 }
64 } else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
65 this.results[0].cancel();
66 }
67 },
68
69 _resback: function (res) {
70 /***
71
72 The primitive that means either callback or errback
73
74 ***/
75 this.fired = ((res instanceof Error) ? 1 : 0);
76 this.results[this.fired] = res;
77 this._fire();
78 },
79
80 _check: function () {
81 if (this.fired != -1) {
82 if (!this.silentlyCancelled) {
83 throw new MochiKit.Async.AlreadyCalledError(this);
84 }
85 this.silentlyCancelled = false;
86 return;
87 }
88 },
89
90 /** @id MochiKit.Async.Deferred.prototype.callback */
91 callback: function (res) {
92 this._check();
93 if (res instanceof MochiKit.Async.Deferred) {
94 throw new Error("Deferred instances can only be chained if they are the result of a callback");
95 }
96 this._resback(res);
97 },
98
99 /** @id MochiKit.Async.Deferred.prototype.errback */
100 errback: function (res) {
101 this._check();
102 var self = MochiKit.Async;
103 if (res instanceof self.Deferred) {
104 throw new Error("Deferred instances can only be chained if they are the result of a callback");
105 }
106 if (!(res instanceof Error)) {
107 res = new self.GenericError(res);
108 }
109 this._resback(res);
110 },
111
112 /** @id MochiKit.Async.Deferred.prototype.addBoth */
113 addBoth: function (fn) {
114 if (arguments.length > 1) {
115 fn = MochiKit.Base.partial.apply(null, arguments);
116 }
117 return this.addCallbacks(fn, fn);
118 },
119
120 /** @id MochiKit.Async.Deferred.prototype.addCallback */
121 addCallback: function (fn) {
122 if (arguments.length > 1) {
123 fn = MochiKit.Base.partial.apply(null, arguments);
124 }
125 return this.addCallbacks(fn, null);
126 },
127
128 /** @id MochiKit.Async.Deferred.prototype.addErrback */
129 addErrback: function (fn) {
130 if (arguments.length > 1) {
131 fn = MochiKit.Base.partial.apply(null, arguments);
132 }
133 return this.addCallbacks(null, fn);
134 },
135
136 /** @id MochiKit.Async.Deferred.prototype.addCallbacks */
137 addCallbacks: function (cb, eb) {
138 if (this.chained) {
139 throw new Error("Chained Deferreds can not be re-used");
140 }
141 this.chain.push([cb, eb]);
142 if (this.fired >= 0) {
143 this._fire();
144 }
145 return this;
146 },
147
148 _fire: function () {
149 /***
150
151 Used internally to exhaust the callback sequence when a result
152 is available.
153
154 ***/
155 var chain = this.chain;
156 var fired = this.fired;
157 var res = this.results[fired];
158 var self = this;
159 var cb = null;
160 while (chain.length > 0 && this.paused === 0) {
161 // Array
162 var pair = chain.shift();
163 var f = pair[fired];
164 if (f === null) {
165 continue;
166 }
167 try {
168 res = f(res);
169 fired = ((res instanceof Error) ? 1 : 0);
170 if (res instanceof MochiKit.Async.Deferred) {
171 cb = function (res) {
172 self._resback(res);
173 self.paused--;
174 if ((self.paused === 0) && (self.fired >= 0)) {
175 self._fire();
176 }
177 };
178 this.paused++;
179 }
180 } catch (err) {
181 fired = 1;
182 if (!(err instanceof Error)) {
183 err = new MochiKit.Async.GenericError(err);
184 }
185 res = err;
186 }
187 }
188 this.fired = fired;
189 this.results[fired] = res;
190 if (cb && this.paused) {
191 // this is for "tail recursion" in case the dependent deferred
192 // is already fired
193 res.addBoth(cb);
194 res.chained = true;
195 }
196 }
197 };
198
199 MochiKit.Base.update(MochiKit.Async, {
200 /** @id MochiKit.Async.evalJSONRequest */
201 evalJSONRequest: function (req) {
202 return MochiKit.Base.evalJSON(req.responseText);
203 },
204
205 /** @id MochiKit.Async.succeed */
206 succeed: function (/* optional */result) {
207 var d = new MochiKit.Async.Deferred();
208 d.callback.apply(d, arguments);
209 return d;
210 },
211
212 /** @id MochiKit.Async.fail */
213 fail: function (/* optional */result) {
214 var d = new MochiKit.Async.Deferred();
215 d.errback.apply(d, arguments);
216 return d;
217 },
218
219 /** @id MochiKit.Async.getXMLHttpRequest */
220 getXMLHttpRequest: function () {
221 var self = arguments.callee;
222 if (!self.XMLHttpRequest) {
223 var tryThese = [
224 function () { return new XMLHttpRequest(); },
225 function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
226 function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
227 function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
228 function () {
229 throw new MochiKit.Async.BrowserComplianceError("Browser does not support XMLHttpRequest");
230 }
231 ];
232 for (var i = 0; i < tryThese.length; i++) {
233 var func = tryThese[i];
234 try {
235 self.XMLHttpRequest = func;
236 return func();
237 } catch (e) {
238 // pass
239 }
240 }
241 }
242 return self.XMLHttpRequest();
243 },
244
245 _xhr_onreadystatechange: function (d) {
246 // MochiKit.Logging.logDebug('this.readyState', this.readyState);
247 var m = MochiKit.Base;
248 if (this.readyState == 4) {
249 // IE SUCKS
250 try {
251 this.onreadystatechange = null;
252 } catch (e) {
253 try {
254 this.onreadystatechange = m.noop;
255 } catch (e) {
256 }
257 }
258 var status = null;
259 try {
260 status = this.status;
261 if (!status && m.isNotEmpty(this.responseText)) {
262 // 0 or undefined seems to mean cached or local
263 status = 304;
264 }
265 } catch (e) {
266 // pass
267 // MochiKit.Logging.logDebug('error getting status?', repr(items(e)));
268 }
269 // 200 is OK, 201 is CREATED, 204 is NO CONTENT
270 // 304 is NOT MODIFIED, 1223 is apparently a bug in IE
271 if (status == 200 || status == 201 || status == 204 ||
272 status == 304 || status == 1223) {
273 d.callback(this);
274 } else {
275 var err = new MochiKit.Async.XMLHttpRequestError(this, "Request failed");
276 if (err.number) {
277 // XXX: This seems to happen on page change
278 d.errback(err);
279 } else {
280 // XXX: this seems to happen when the server is unreachable
281 d.errback(err);
282 }
283 }
284 }
285 },
286
287 _xhr_canceller: function (req) {
288 // IE SUCKS
289 try {
290 req.onreadystatechange = null;
291 } catch (e) {
292 try {
293 req.onreadystatechange = MochiKit.Base.noop;
294 } catch (e) {
295 }
296 }
297 req.abort();
298 },
299
300
301 /** @id MochiKit.Async.sendXMLHttpRequest */
302 sendXMLHttpRequest: function (req, /* optional */ sendContent) {
303 if (typeof(sendContent) == "undefined" || sendContent === null) {
304 sendContent = "";
305 }
306
307 var m = MochiKit.Base;
308 var self = MochiKit.Async;
309 var d = new self.Deferred(m.partial(self._xhr_canceller, req));
310
311 try {
312 req.onreadystatechange = m.bind(self._xhr_onreadystatechange,
313 req, d);
314 req.send(sendContent);
315 } catch (e) {
316 try {
317 req.onreadystatechange = null;
318 } catch (ignore) {
319 // pass
320 }
321 d.errback(e);
322 }
323
324 return d;
325
326 },
327
328 /** @id MochiKit.Async.doXHR */
329 doXHR: function (url, opts) {
330 /*
331 Work around a Firefox bug by dealing with XHR during
332 the next event loop iteration. Maybe it's this one:
333 https://bugzilla.mozilla.org/show_bug.cgi?id=249843
334 */
335 var self = MochiKit.Async;
336 return self.callLater(0, self._doXHR, url, opts);
337 },
338
339 _doXHR: function (url, opts) {
340 var m = MochiKit.Base;
341 opts = m.update({
342 method: 'GET',
343 sendContent: ''
344 /*
345 queryString: undefined,
346 username: undefined,
347 password: undefined,
348 headers: undefined,
349 mimeType: undefined
350 */
351 }, opts);
352 var self = MochiKit.Async;
353 var req = self.getXMLHttpRequest();
354 if (opts.queryString) {
355 var qs = m.queryString(opts.queryString);
356 if (qs) {
357 url += "?" + qs;
358 }
359 }
360 // Safari will send undefined:undefined, so we have to check.
361 // We can't use apply, since the function is native.
362 if ('username' in opts) {
363 req.open(opts.method, url, true, opts.username, opts.password);
364 } else {
365 req.open(opts.method, url, true);
366 }
367 if (req.overrideMimeType && opts.mimeType) {
368 req.overrideMimeType(opts.mimeType);
369 }
370 req.setRequestHeader("X-Requested-With", "XMLHttpRequest");
371 if (opts.headers) {
372 var headers = opts.headers;
373 if (!m.isArrayLike(headers)) {
374 headers = m.items(headers);
375 }
376 for (var i = 0; i < headers.length; i++) {
377 var header = headers[i];
378 var name = header[0];
379 var value = header[1];
380 req.setRequestHeader(name, value);
381 }
382 }
383 return self.sendXMLHttpRequest(req, opts.sendContent);
384 },
385
386 _buildURL: function (url/*, ...*/) {
387 if (arguments.length > 1) {
388 var m = MochiKit.Base;
389 var qs = m.queryString.apply(null, m.extend(null, arguments, 1));
390 if (qs) {
391 return url + "?" + qs;
392 }
393 }
394 return url;
395 },
396
397 /** @id MochiKit.Async.doSimpleXMLHttpRequest */
398 doSimpleXMLHttpRequest: function (url/*, ...*/) {
399 var self = MochiKit.Async;
400 url = self._buildURL.apply(self, arguments);
401 return self.doXHR(url);
402 },
403
404 /** @id MochiKit.Async.loadJSONDoc */
405 loadJSONDoc: function (url/*, ...*/) {
406 var self = MochiKit.Async;
407 url = self._buildURL.apply(self, arguments);
408 var d = self.doXHR(url, {
409 'mimeType': 'text/plain',
410 'headers': [['Accept', 'application/json']]
411 });
412 d = d.addCallback(self.evalJSONRequest);
413 return d;
414 },
415
416 /** @id MochiKit.Async.wait */
417 wait: function (seconds, /* optional */value) {
418 var d = new MochiKit.Async.Deferred();
419 var m = MochiKit.Base;
420 if (typeof(value) != 'undefined') {
421 d.addCallback(function () { return value; });
422 }
423 var timeout = setTimeout(
424 m.bind("callback", d),
425 Math.floor(seconds * 1000));
426 d.canceller = function () {
427 try {
428 clearTimeout(timeout);
429 } catch (e) {
430 // pass
431 }
432 };
433 return d;
434 },
435
436 /** @id MochiKit.Async.callLater */
437 callLater: function (seconds, func) {
438 var m = MochiKit.Base;
439 var pfunc = m.partial.apply(m, m.extend(null, arguments, 1));
440 return MochiKit.Async.wait(seconds).addCallback(
441 function (res) { return pfunc(); }
442 );
443 }
444 });
445
446
447 /** @id MochiKit.Async.DeferredLock */
448 MochiKit.Async.DeferredLock = function () {
449 this.waiting = [];
450 this.locked = false;
451 this.id = this._nextId();
452 };
453
454 MochiKit.Async.DeferredLock.prototype = {
455 __class__: MochiKit.Async.DeferredLock,
456 /** @id MochiKit.Async.DeferredLock.prototype.acquire */
457 acquire: function () {
458 var d = new MochiKit.Async.Deferred();
459 if (this.locked) {
460 this.waiting.push(d);
461 } else {
462 this.locked = true;
463 d.callback(this);
464 }
465 return d;
466 },
467 /** @id MochiKit.Async.DeferredLock.prototype.release */
468 release: function () {
469 if (!this.locked) {
470 throw TypeError("Tried to release an unlocked DeferredLock");
471 }
472 this.locked = false;
473 if (this.waiting.length > 0) {
474 this.locked = true;
475 this.waiting.shift().callback(this);
476 }
477 },
478 _nextId: MochiKit.Base.counter(),
479 repr: function () {
480 var state;
481 if (this.locked) {
482 state = 'locked, ' + this.waiting.length + ' waiting';
483 } else {
484 state = 'unlocked';
485 }
486 return 'DeferredLock(' + this.id + ', ' + state + ')';
487 },
488 toString: MochiKit.Base.forwardCall("repr")
489
490 };
491
492 /** @id MochiKit.Async.DeferredList */
493 MochiKit.Async.DeferredList = function (list, /* optional */fireOnOneCallback, fireOnOneErrback, consumeErrors, canceller) {
494
495 // call parent constructor
496 MochiKit.Async.Deferred.apply(this, [canceller]);
497
498 this.list = list;
499 var resultList = [];
500 this.resultList = resultList;
501
502 this.finishedCount = 0;
503 this.fireOnOneCallback = fireOnOneCallback;
504 this.fireOnOneErrback = fireOnOneErrback;
505 this.consumeErrors = consumeErrors;
506
507 var cb = MochiKit.Base.bind(this._cbDeferred, this);
508 for (var i = 0; i < list.length; i++) {
509 var d = list[i];
510 resultList.push(undefined);
511 d.addCallback(cb, i, true);
512 d.addErrback(cb, i, false);
513 }
514
515 if (list.length === 0 && !fireOnOneCallback) {
516 this.callback(this.resultList);
517 }
518
519 };
520
521 MochiKit.Async.DeferredList.prototype = new MochiKit.Async.Deferred();
522
523 MochiKit.Async.DeferredList.prototype._cbDeferred = function (index, succeeded, result) {
524 this.resultList[index] = [succeeded, result];
525 this.finishedCount += 1;
526 if (this.fired == -1) {
527 if (succeeded && this.fireOnOneCallback) {
528 this.callback([index, result]);
529 } else if (!succeeded && this.fireOnOneErrback) {
530 this.errback(result);
531 } else if (this.finishedCount == this.list.length) {
532 this.callback(this.resultList);
533 }
534 }
535 if (!succeeded && this.consumeErrors) {
536 result = null;
537 }
538 return result;
539 };
540
541 /** @id MochiKit.Async.gatherResults */
542 MochiKit.Async.gatherResults = function (deferredList) {
543 var d = new MochiKit.Async.DeferredList(deferredList, false, true, false);
544 d.addCallback(function (results) {
545 var ret = [];
546 for (var i = 0; i < results.length; i++) {
547 ret.push(results[i][1]);
548 }
549 return ret;
550 });
551 return d;
552 };
553
554 /** @id MochiKit.Async.maybeDeferred */
555 MochiKit.Async.maybeDeferred = function (func) {
556 var self = MochiKit.Async;
557 var result;
558 try {
559 var r = func.apply(null, MochiKit.Base.extend([], arguments, 1));
560 if (r instanceof self.Deferred) {
561 result = r;
562 } else if (r instanceof Error) {
563 result = self.fail(r);
564 } else {
565 result = self.succeed(r);
566 }
567 } catch (e) {
568 result = self.fail(e);
569 }
570 return result;
571 };
572
573
574 MochiKit.Async.EXPORT = [
575 "AlreadyCalledError",
576 "CancelledError",
577 "BrowserComplianceError",
578 "GenericError",
579 "XMLHttpRequestError",
580 "Deferred",
581 "succeed",
582 "fail",
583 "getXMLHttpRequest",
584 "doSimpleXMLHttpRequest",
585 "loadJSONDoc",
586 "wait",
587 "callLater",
588 "sendXMLHttpRequest",
589 "DeferredLock",
590 "DeferredList",
591 "gatherResults",
592 "maybeDeferred",
593 "doXHR"
594 ];
595
596 MochiKit.Async.EXPORT_OK = [
597 "evalJSONRequest"
598 ];
599
600 MochiKit.Async.__new__ = function () {
601 var m = MochiKit.Base;
602 var ne = m.partial(m._newNamedError, this);
603
604 ne("AlreadyCalledError",
605 /** @id MochiKit.Async.AlreadyCalledError */
606 function (deferred) {
607 /***
608
609 Raised by the Deferred if callback or errback happens
610 after it was already fired.
611
612 ***/
613 this.deferred = deferred;
614 }
615 );
616
617 ne("CancelledError",
618 /** @id MochiKit.Async.CancelledError */
619 function (deferred) {
620 /***
621
622 Raised by the Deferred cancellation mechanism.
623
624 ***/
625 this.deferred = deferred;
626 }
627 );
628
629 ne("BrowserComplianceError",
630 /** @id MochiKit.Async.BrowserComplianceError */
631 function (msg) {
632 /***
633
634 Raised when the JavaScript runtime is not capable of performing
635 the given function. Technically, this should really never be
636 raised because a non-conforming JavaScript runtime probably
637 isn't going to support exceptions in the first place.
638
639 ***/
640 this.message = msg;
641 }
642 );
643
644 ne("GenericError",
645 /** @id MochiKit.Async.GenericError */
646 function (msg) {
647 this.message = msg;
648 }
649 );
650
651 ne("XMLHttpRequestError",
652 /** @id MochiKit.Async.XMLHttpRequestError */
653 function (req, msg) {
654 /***
655
656 Raised when an XMLHttpRequest does not complete for any reason.
657
658 ***/
659 this.req = req;
660 this.message = msg;
661 try {
662 // Strange but true that this can raise in some cases.
663 this.number = req.status;
664 } catch (e) {
665 // pass
666 }
667 }
668 );
669
670
671 this.EXPORT_TAGS = {
672 ":common": this.EXPORT,
673 ":all": m.concat(this.EXPORT, this.EXPORT_OK)
674 };
675
676 m.nameFunctions(this);
677
678 };
679
680 MochiKit.Async.__new__();
681
682 MochiKit.Base._exportSymbols(this, MochiKit.Async);

mercurial