Async.js 16 KB

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