Async.js 19 KB

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