Deferred.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. define([
  2. "./has",
  3. "./_base/lang",
  4. "./errors/CancelError",
  5. "./promise/Promise",
  6. "./has!config-deferredInstrumentation?./promise/instrumentation"
  7. ], function(has, lang, CancelError, Promise, instrumentation){
  8. "use strict";
  9. // module:
  10. // dojo/Deferred
  11. var PROGRESS = 0,
  12. RESOLVED = 1,
  13. REJECTED = 2;
  14. var FULFILLED_ERROR_MESSAGE = "This deferred has already been fulfilled.";
  15. var freezeObject = Object.freeze || function(){};
  16. var signalWaiting = function(waiting, type, result, rejection, deferred){
  17. if(has("config-deferredInstrumentation")){
  18. if(type === REJECTED && Deferred.instrumentRejected && waiting.length === 0){
  19. Deferred.instrumentRejected(result, false, rejection, deferred);
  20. }
  21. }
  22. for(var i = 0; i < waiting.length; i++){
  23. signalListener(waiting[i], type, result, rejection);
  24. }
  25. };
  26. var signalListener = function(listener, type, result, rejection){
  27. var func = listener[type];
  28. var deferred = listener.deferred;
  29. if(func){
  30. try{
  31. var newResult = func(result);
  32. if(type === PROGRESS){
  33. if(typeof newResult !== "undefined"){
  34. signalDeferred(deferred, type, newResult);
  35. }
  36. }else{
  37. if(newResult && typeof newResult.then === "function"){
  38. listener.cancel = newResult.cancel;
  39. newResult.then(
  40. // Only make resolvers if they're actually going to be used
  41. makeDeferredSignaler(deferred, RESOLVED),
  42. makeDeferredSignaler(deferred, REJECTED),
  43. makeDeferredSignaler(deferred, PROGRESS));
  44. return;
  45. }
  46. signalDeferred(deferred, RESOLVED, newResult);
  47. }
  48. }catch(error){
  49. signalDeferred(deferred, REJECTED, error);
  50. }
  51. }else{
  52. signalDeferred(deferred, type, result);
  53. }
  54. if(has("config-deferredInstrumentation")){
  55. if(type === REJECTED && Deferred.instrumentRejected){
  56. Deferred.instrumentRejected(result, !!func, rejection, deferred.promise);
  57. }
  58. }
  59. };
  60. var makeDeferredSignaler = function(deferred, type){
  61. return function(value){
  62. signalDeferred(deferred, type, value);
  63. };
  64. };
  65. var signalDeferred = function(deferred, type, result){
  66. if(!deferred.isCanceled()){
  67. switch(type){
  68. case PROGRESS:
  69. deferred.progress(result);
  70. break;
  71. case RESOLVED:
  72. deferred.resolve(result);
  73. break;
  74. case REJECTED:
  75. deferred.reject(result);
  76. break;
  77. }
  78. }
  79. };
  80. var Deferred = function(canceler){
  81. // summary:
  82. // Creates a new deferred. This API is preferred over
  83. // `dojo/_base/Deferred`.
  84. // description:
  85. // Creates a new deferred, as an abstraction over (primarily)
  86. // asynchronous operations. The deferred is the private interface
  87. // that should not be returned to calling code. That's what the
  88. // `promise` is for. See `dojo/promise/Promise`.
  89. // canceler: Function?
  90. // Will be invoked if the deferred is canceled. The canceler
  91. // receives the reason the deferred was canceled as its argument.
  92. // The deferred is rejected with its return value, or a new
  93. // `dojo/errors/CancelError` instance.
  94. // promise: dojo/promise/Promise
  95. // The public promise object that clients can add callbacks to.
  96. var promise = this.promise = new Promise();
  97. var deferred = this;
  98. var fulfilled, result, rejection;
  99. var canceled = false;
  100. var waiting = [];
  101. if(has("config-deferredInstrumentation") && Error.captureStackTrace){
  102. Error.captureStackTrace(deferred, Deferred);
  103. Error.captureStackTrace(promise, Deferred);
  104. }
  105. this.isResolved = promise.isResolved = function(){
  106. // summary:
  107. // Checks whether the deferred has been resolved.
  108. // returns: Boolean
  109. return fulfilled === RESOLVED;
  110. };
  111. this.isRejected = promise.isRejected = function(){
  112. // summary:
  113. // Checks whether the deferred has been rejected.
  114. // returns: Boolean
  115. return fulfilled === REJECTED;
  116. };
  117. this.isFulfilled = promise.isFulfilled = function(){
  118. // summary:
  119. // Checks whether the deferred has been resolved or rejected.
  120. // returns: Boolean
  121. return !!fulfilled;
  122. };
  123. this.isCanceled = promise.isCanceled = function(){
  124. // summary:
  125. // Checks whether the deferred has been canceled.
  126. // returns: Boolean
  127. return canceled;
  128. };
  129. this.progress = function(update, strict){
  130. // summary:
  131. // Emit a progress update on the deferred.
  132. // description:
  133. // Emit a progress update on the deferred. Progress updates
  134. // can be used to communicate updates about the asynchronous
  135. // operation before it has finished.
  136. // update: any
  137. // The progress update. Passed to progbacks.
  138. // strict: Boolean?
  139. // If strict, will throw an error if the deferred has already
  140. // been fulfilled and consequently no progress can be emitted.
  141. // returns: dojo/promise/Promise
  142. // Returns the original promise for the deferred.
  143. if(!fulfilled){
  144. signalWaiting(waiting, PROGRESS, update, null, deferred);
  145. return promise;
  146. }else if(strict === true){
  147. throw new Error(FULFILLED_ERROR_MESSAGE);
  148. }else{
  149. return promise;
  150. }
  151. };
  152. this.resolve = function(value, strict){
  153. // summary:
  154. // Resolve the deferred.
  155. // description:
  156. // Resolve the deferred, putting it in a success state.
  157. // value: any
  158. // The result of the deferred. Passed to callbacks.
  159. // strict: Boolean?
  160. // If strict, will throw an error if the deferred has already
  161. // been fulfilled and consequently cannot be resolved.
  162. // returns: dojo/promise/Promise
  163. // Returns the original promise for the deferred.
  164. if(!fulfilled){
  165. // Set fulfilled, store value. After signaling waiting listeners unset
  166. // waiting.
  167. signalWaiting(waiting, fulfilled = RESOLVED, result = value, null, deferred);
  168. waiting = null;
  169. return promise;
  170. }else if(strict === true){
  171. throw new Error(FULFILLED_ERROR_MESSAGE);
  172. }else{
  173. return promise;
  174. }
  175. };
  176. var reject = this.reject = function(error, strict){
  177. // summary:
  178. // Reject the deferred.
  179. // description:
  180. // Reject the deferred, putting it in an error state.
  181. // error: any
  182. // The error result of the deferred. Passed to errbacks.
  183. // strict: Boolean?
  184. // If strict, will throw an error if the deferred has already
  185. // been fulfilled and consequently cannot be rejected.
  186. // returns: dojo/promise/Promise
  187. // Returns the original promise for the deferred.
  188. if(!fulfilled){
  189. if(has("config-deferredInstrumentation") && Error.captureStackTrace){
  190. Error.captureStackTrace(rejection = {}, reject);
  191. }
  192. signalWaiting(waiting, fulfilled = REJECTED, result = error, rejection, deferred);
  193. waiting = null;
  194. return promise;
  195. }else if(strict === true){
  196. throw new Error(FULFILLED_ERROR_MESSAGE);
  197. }else{
  198. return promise;
  199. }
  200. };
  201. this.then = promise.then = function(callback, errback, progback){
  202. // summary:
  203. // Add new callbacks to the deferred.
  204. // description:
  205. // Add new callbacks to the deferred. Callbacks can be added
  206. // before or after the deferred is fulfilled.
  207. // callback: Function?
  208. // Callback to be invoked when the promise is resolved.
  209. // Receives the resolution value.
  210. // errback: Function?
  211. // Callback to be invoked when the promise is rejected.
  212. // Receives the rejection error.
  213. // progback: Function?
  214. // Callback to be invoked when the promise emits a progress
  215. // update. Receives the progress update.
  216. // returns: dojo/promise/Promise
  217. // Returns a new promise for the result of the callback(s).
  218. // This can be used for chaining many asynchronous operations.
  219. var listener = [progback, callback, errback];
  220. // Ensure we cancel the promise we're waiting for, or if callback/errback
  221. // have returned a promise, cancel that one.
  222. listener.cancel = promise.cancel;
  223. listener.deferred = new Deferred(function(reason){
  224. // Check whether cancel is really available, returned promises are not
  225. // required to expose `cancel`
  226. return listener.cancel && listener.cancel(reason);
  227. });
  228. if(fulfilled && !waiting){
  229. signalListener(listener, fulfilled, result, rejection);
  230. }else{
  231. waiting.push(listener);
  232. }
  233. return listener.deferred.promise;
  234. };
  235. this.cancel = promise.cancel = function(reason, strict){
  236. // summary:
  237. // Inform the deferred it may cancel its asynchronous operation.
  238. // description:
  239. // Inform the deferred it may cancel its asynchronous operation.
  240. // The deferred's (optional) canceler is invoked and the
  241. // deferred will be left in a rejected state. Can affect other
  242. // promises that originate with the same deferred.
  243. // reason: any
  244. // A message that may be sent to the deferred's canceler,
  245. // explaining why it's being canceled.
  246. // strict: Boolean?
  247. // If strict, will throw an error if the deferred has already
  248. // been fulfilled and consequently cannot be canceled.
  249. // returns: any
  250. // Returns the rejection reason if the deferred was canceled
  251. // normally.
  252. if(!fulfilled){
  253. // Cancel can be called even after the deferred is fulfilled
  254. if(canceler){
  255. var returnedReason = canceler(reason);
  256. reason = typeof returnedReason === "undefined" ? reason : returnedReason;
  257. }
  258. canceled = true;
  259. if(!fulfilled){
  260. // Allow canceler to provide its own reason, but fall back to a CancelError
  261. if(typeof reason === "undefined"){
  262. reason = new CancelError();
  263. }
  264. reject(reason);
  265. return reason;
  266. }else if(fulfilled === REJECTED && result === reason){
  267. return reason;
  268. }
  269. }else if(strict === true){
  270. throw new Error(FULFILLED_ERROR_MESSAGE);
  271. }
  272. };
  273. freezeObject(promise);
  274. };
  275. Deferred.prototype.toString = function(){
  276. // returns: String
  277. // Returns `[object Deferred]`.
  278. return "[object Deferred]";
  279. };
  280. if(instrumentation){
  281. instrumentation(Deferred);
  282. }
  283. return Deferred;
  284. });