Deferred.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojo._base.Deferred"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojo._base.Deferred"] = true;
  8. dojo.provide("dojo._base.Deferred");
  9. dojo.require("dojo._base.lang");
  10. (function(){
  11. var mutator = function(){};
  12. var freeze = Object.freeze || function(){};
  13. // A deferred provides an API for creating and resolving a promise.
  14. dojo.Deferred = function(/*Function?*/canceller){
  15. // summary:
  16. // Deferreds provide a generic means for encapsulating an asynchronous
  17. // operation and notifying users of the completion and result of the operation.
  18. // description:
  19. // The dojo.Deferred API is based on the concept of promises that provide a
  20. // generic interface into the eventual completion of an asynchronous action.
  21. // The motivation for promises fundamentally is about creating a
  22. // separation of concerns that allows one to achieve the same type of
  23. // call patterns and logical data flow in asynchronous code as can be
  24. // achieved in synchronous code. Promises allows one
  25. // to be able to call a function purely with arguments needed for
  26. // execution, without conflating the call with concerns of whether it is
  27. // sync or async. One shouldn't need to alter a call's arguments if the
  28. // implementation switches from sync to async (or vice versa). By having
  29. // async functions return promises, the concerns of making the call are
  30. // separated from the concerns of asynchronous interaction (which are
  31. // handled by the promise).
  32. //
  33. // The dojo.Deferred is a type of promise that provides methods for fulfilling the
  34. // promise with a successful result or an error. The most important method for
  35. // working with Dojo's promises is the then() method, which follows the
  36. // CommonJS proposed promise API. An example of using a Dojo promise:
  37. //
  38. // | var resultingPromise = someAsyncOperation.then(function(result){
  39. // | ... handle result ...
  40. // | },
  41. // | function(error){
  42. // | ... handle error ...
  43. // | });
  44. //
  45. // The .then() call returns a new promise that represents the result of the
  46. // execution of the callback. The callbacks will never affect the original promises value.
  47. //
  48. // The dojo.Deferred instances also provide the following functions for backwards compatibility:
  49. //
  50. // * addCallback(handler)
  51. // * addErrback(handler)
  52. // * callback(result)
  53. // * errback(result)
  54. //
  55. // Callbacks are allowed to return promises themselves, so
  56. // you can build complicated sequences of events with ease.
  57. //
  58. // The creator of the Deferred may specify a canceller. The canceller
  59. // is a function that will be called if Deferred.cancel is called
  60. // before the Deferred fires. You can use this to implement clean
  61. // aborting of an XMLHttpRequest, etc. Note that cancel will fire the
  62. // deferred with a CancelledError (unless your canceller returns
  63. // another kind of error), so the errbacks should be prepared to
  64. // handle that error for cancellable Deferreds.
  65. // example:
  66. // | var deferred = new dojo.Deferred();
  67. // | setTimeout(function(){ deferred.callback({success: true}); }, 1000);
  68. // | return deferred;
  69. // example:
  70. // Deferred objects are often used when making code asynchronous. It
  71. // may be easiest to write functions in a synchronous manner and then
  72. // split code using a deferred to trigger a response to a long-lived
  73. // operation. For example, instead of register a callback function to
  74. // denote when a rendering operation completes, the function can
  75. // simply return a deferred:
  76. //
  77. // | // callback style:
  78. // | function renderLotsOfData(data, callback){
  79. // | var success = false
  80. // | try{
  81. // | for(var x in data){
  82. // | renderDataitem(data[x]);
  83. // | }
  84. // | success = true;
  85. // | }catch(e){ }
  86. // | if(callback){
  87. // | callback(success);
  88. // | }
  89. // | }
  90. //
  91. // | // using callback style
  92. // | renderLotsOfData(someDataObj, function(success){
  93. // | // handles success or failure
  94. // | if(!success){
  95. // | promptUserToRecover();
  96. // | }
  97. // | });
  98. // | // NOTE: no way to add another callback here!!
  99. // example:
  100. // Using a Deferred doesn't simplify the sending code any, but it
  101. // provides a standard interface for callers and senders alike,
  102. // providing both with a simple way to service multiple callbacks for
  103. // an operation and freeing both sides from worrying about details
  104. // such as "did this get called already?". With Deferreds, new
  105. // callbacks can be added at any time.
  106. //
  107. // | // Deferred style:
  108. // | function renderLotsOfData(data){
  109. // | var d = new dojo.Deferred();
  110. // | try{
  111. // | for(var x in data){
  112. // | renderDataitem(data[x]);
  113. // | }
  114. // | d.callback(true);
  115. // | }catch(e){
  116. // | d.errback(new Error("rendering failed"));
  117. // | }
  118. // | return d;
  119. // | }
  120. //
  121. // | // using Deferred style
  122. // | renderLotsOfData(someDataObj).then(null, function(){
  123. // | promptUserToRecover();
  124. // | });
  125. // | // NOTE: addErrback and addCallback both return the Deferred
  126. // | // again, so we could chain adding callbacks or save the
  127. // | // deferred for later should we need to be notified again.
  128. // example:
  129. // In this example, renderLotsOfData is synchronous and so both
  130. // versions are pretty artificial. Putting the data display on a
  131. // timeout helps show why Deferreds rock:
  132. //
  133. // | // Deferred style and async func
  134. // | function renderLotsOfData(data){
  135. // | var d = new dojo.Deferred();
  136. // | setTimeout(function(){
  137. // | try{
  138. // | for(var x in data){
  139. // | renderDataitem(data[x]);
  140. // | }
  141. // | d.callback(true);
  142. // | }catch(e){
  143. // | d.errback(new Error("rendering failed"));
  144. // | }
  145. // | }, 100);
  146. // | return d;
  147. // | }
  148. //
  149. // | // using Deferred style
  150. // | renderLotsOfData(someDataObj).then(null, function(){
  151. // | promptUserToRecover();
  152. // | });
  153. //
  154. // Note that the caller doesn't have to change his code at all to
  155. // handle the asynchronous case.
  156. var result, finished, isError, head, nextListener;
  157. var promise = (this.promise = {});
  158. function complete(value){
  159. if(finished){
  160. throw new Error("This deferred has already been resolved");
  161. }
  162. result = value;
  163. finished = true;
  164. notify();
  165. }
  166. function notify(){
  167. var mutated;
  168. while(!mutated && nextListener){
  169. var listener = nextListener;
  170. nextListener = nextListener.next;
  171. if((mutated = (listener.progress == mutator))){ // assignment and check
  172. finished = false;
  173. }
  174. var func = (isError ? listener.error : listener.resolved);
  175. if (func) {
  176. try {
  177. var newResult = func(result);
  178. if (newResult && typeof newResult.then === "function") {
  179. newResult.then(dojo.hitch(listener.deferred, "resolve"), dojo.hitch(listener.deferred, "reject"));
  180. continue;
  181. }
  182. var unchanged = mutated && newResult === undefined;
  183. if(mutated && !unchanged){
  184. isError = newResult instanceof Error;
  185. }
  186. listener.deferred[unchanged && isError ? "reject" : "resolve"](unchanged ? result : newResult);
  187. }
  188. catch (e) {
  189. listener.deferred.reject(e);
  190. }
  191. }else {
  192. if(isError){
  193. listener.deferred.reject(result);
  194. }else{
  195. listener.deferred.resolve(result);
  196. }
  197. }
  198. }
  199. }
  200. // calling resolve will resolve the promise
  201. this.resolve = this.callback = function(value){
  202. // summary:
  203. // Fulfills the Deferred instance successfully with the provide value
  204. this.fired = 0;
  205. this.results = [value, null];
  206. complete(value);
  207. };
  208. // calling error will indicate that the promise failed
  209. this.reject = this.errback = function(error){
  210. // summary:
  211. // Fulfills the Deferred instance as an error with the provided error
  212. isError = true;
  213. this.fired = 1;
  214. complete(error);
  215. this.results = [null, error];
  216. if(!error || error.log !== false){
  217. (dojo.config.deferredOnError || function(x){ console.error(x); })(error);
  218. }
  219. };
  220. // call progress to provide updates on the progress on the completion of the promise
  221. this.progress = function(update){
  222. // summary
  223. // Send progress events to all listeners
  224. var listener = nextListener;
  225. while(listener){
  226. var progress = listener.progress;
  227. progress && progress(update);
  228. listener = listener.next;
  229. }
  230. };
  231. this.addCallbacks = function(/*Function?*/callback, /*Function?*/errback){
  232. this.then(callback, errback, mutator);
  233. return this;
  234. };
  235. // provide the implementation of the promise
  236. this.then = promise.then = function(/*Function?*/resolvedCallback, /*Function?*/errorCallback, /*Function?*/progressCallback){
  237. // summary:
  238. // Adds a fulfilledHandler, errorHandler, and progressHandler to be called for
  239. // completion of a promise. The fulfilledHandler is called when the promise
  240. // is fulfilled. The errorHandler is called when a promise fails. The
  241. // progressHandler is called for progress events. All arguments are optional
  242. // and non-function values are ignored. The progressHandler is not only an
  243. // optional argument, but progress events are purely optional. Promise
  244. // providers are not required to ever create progress events.
  245. //
  246. // This function will return a new promise that is fulfilled when the given
  247. // fulfilledHandler or errorHandler callback is finished. This allows promise
  248. // operations to be chained together. The value returned from the callback
  249. // handler is the fulfillment value for the returned promise. If the callback
  250. // throws an error, the returned promise will be moved to failed state.
  251. //
  252. // example:
  253. // An example of using a CommonJS compliant promise:
  254. // | asyncComputeTheAnswerToEverything().
  255. // | then(addTwo).
  256. // | then(printResult, onError);
  257. // | >44
  258. //
  259. var returnDeferred = progressCallback == mutator ? this : new dojo.Deferred(promise.cancel);
  260. var listener = {
  261. resolved: resolvedCallback,
  262. error: errorCallback,
  263. progress: progressCallback,
  264. deferred: returnDeferred
  265. };
  266. if(nextListener){
  267. head = head.next = listener;
  268. }
  269. else{
  270. nextListener = head = listener;
  271. }
  272. if(finished){
  273. notify();
  274. }
  275. return returnDeferred.promise;
  276. };
  277. var deferred = this;
  278. this.cancel = promise.cancel = function () {
  279. // summary:
  280. // Cancels the asynchronous operation
  281. if(!finished){
  282. var error = canceller && canceller(deferred);
  283. if(!finished){
  284. if (!(error instanceof Error)) {
  285. error = new Error(error);
  286. }
  287. error.log = false;
  288. deferred.reject(error);
  289. }
  290. }
  291. };
  292. freeze(promise);
  293. };
  294. dojo.extend(dojo.Deferred, {
  295. addCallback: function (/*Function*/callback) {
  296. return this.addCallbacks(dojo.hitch.apply(dojo, arguments));
  297. },
  298. addErrback: function (/*Function*/errback) {
  299. return this.addCallbacks(null, dojo.hitch.apply(dojo, arguments));
  300. },
  301. addBoth: function (/*Function*/callback) {
  302. var enclosed = dojo.hitch.apply(dojo, arguments);
  303. return this.addCallbacks(enclosed, enclosed);
  304. },
  305. fired: -1
  306. });
  307. })();
  308. dojo.when = function(promiseOrValue, /*Function?*/callback, /*Function?*/errback, /*Function?*/progressHandler){
  309. // summary:
  310. // This provides normalization between normal synchronous values and
  311. // asynchronous promises, so you can interact with them in a common way
  312. // example:
  313. // | function printFirstAndList(items){
  314. // | dojo.when(findFirst(items), console.log);
  315. // | dojo.when(findLast(items), console.log);
  316. // | }
  317. // | function findFirst(items){
  318. // | return dojo.when(items, function(items){
  319. // | return items[0];
  320. // | });
  321. // | }
  322. // | function findLast(items){
  323. // | return dojo.when(items, function(items){
  324. // | return items[items.length];
  325. // | });
  326. // | }
  327. // And now all three of his functions can be used sync or async.
  328. // | printFirstAndLast([1,2,3,4]) will work just as well as
  329. // | printFirstAndLast(dojo.xhrGet(...));
  330. if(promiseOrValue && typeof promiseOrValue.then === "function"){
  331. return promiseOrValue.then(callback, errback, progressHandler);
  332. }
  333. return callback(promiseOrValue);
  334. };
  335. }