resultutil.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. // Copyright 2012 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview This file provides primitives and tools (wait, transform,
  16. * chain, combine) that make it easier to work with Results. This section
  17. * gives an overview of their functionality along with some examples and the
  18. * actual definitions have detailed descriptions next to them.
  19. *
  20. *
  21. * NOTE: goog.result is soft deprecated - we expect to replace this and
  22. * goog.async.Deferred with a wrapper around W3C Promises:
  23. * http://dom.spec.whatwg.org/#promises.
  24. */
  25. goog.provide('goog.result');
  26. goog.require('goog.array');
  27. goog.require('goog.result.DependentResult');
  28. goog.require('goog.result.Result');
  29. goog.require('goog.result.SimpleResult');
  30. /**
  31. * Returns a successful result containing the provided value.
  32. *
  33. * Example:
  34. * <pre>
  35. *
  36. * var value = 'some-value';
  37. * var result = goog.result.immediateResult(value);
  38. * assertEquals(goog.result.Result.State.SUCCESS, result.getState());
  39. * assertEquals(value, result.getValue());
  40. *
  41. * </pre>
  42. *
  43. * @param {*} value The value of the result.
  44. * @return {!goog.result.Result} A Result object that has already been resolved
  45. * to the supplied value.
  46. */
  47. goog.result.successfulResult = function(value) {
  48. var result = new goog.result.SimpleResult();
  49. result.setValue(value);
  50. return result;
  51. };
  52. /**
  53. * Returns a failed result with the optional error slug set.
  54. *
  55. * Example:
  56. * <pre>
  57. *
  58. * var error = new Error('something-failed');
  59. * var result = goog.result.failedResult(error);
  60. * assertEquals(goog.result.Result.State.ERROR, result.getState());
  61. * assertEquals(error, result.getError());
  62. *
  63. * </pre>
  64. *
  65. * @param {*=} opt_error The error to which the result should resolve.
  66. * @return {!goog.result.Result} A Result object that has already been resolved
  67. * to the supplied Error.
  68. */
  69. goog.result.failedResult = function(opt_error) {
  70. var result = new goog.result.SimpleResult();
  71. result.setError(opt_error);
  72. return result;
  73. };
  74. /**
  75. * Returns a canceled result.
  76. * The result will be resolved to an error of type CancelError.
  77. *
  78. * Example:
  79. * <pre>
  80. *
  81. * var result = goog.result.canceledResult();
  82. * assertEquals(goog.result.Result.State.ERROR, result.getState());
  83. * var error = result.getError();
  84. * assertTrue(error instanceof goog.result.Result.CancelError);
  85. *
  86. * </pre>
  87. *
  88. * @return {!goog.result.Result} A canceled Result.
  89. */
  90. goog.result.canceledResult = function() {
  91. var result = new goog.result.SimpleResult();
  92. result.cancel();
  93. return result;
  94. };
  95. /**
  96. * Calls the handler on resolution of the result (success or failure).
  97. * The handler is passed the result object as the only parameter. The call will
  98. * be immediate if the result is no longer pending.
  99. *
  100. * Example:
  101. * <pre>
  102. *
  103. * var result = xhr.get('testdata/xhr_test_text.data');
  104. *
  105. * // Wait for the result to be resolved and alert it's state.
  106. * goog.result.wait(result, function(result) {
  107. * alert('State: ' + result.getState());
  108. * });
  109. * </pre>
  110. *
  111. * @param {!goog.result.Result} result The result to install the handlers.
  112. * @param {function(this:T, !goog.result.Result)} handler The handler to be
  113. * called. The handler is passed the result object as the only parameter.
  114. * @param {T=} opt_scope Optional scope for the handler.
  115. * @template T
  116. */
  117. goog.result.wait = function(result, handler, opt_scope) {
  118. result.wait(handler, opt_scope);
  119. };
  120. /**
  121. * Calls the handler if the result succeeds. The result object is the only
  122. * parameter passed to the handler. The call will be immediate if the result
  123. * has already succeeded.
  124. *
  125. * Example:
  126. * <pre>
  127. *
  128. * var result = xhr.get('testdata/xhr_test_text.data');
  129. *
  130. * // attach a success handler.
  131. * goog.result.waitOnSuccess(result, function(resultValue, result) {
  132. * var datavalue = result.getvalue();
  133. * alert('value: ' + datavalue + ' == ' + resultValue);
  134. * });
  135. * </pre>
  136. *
  137. * @param {!goog.result.Result} result The result to install the handlers.
  138. * @param {function(this:T, ?, !goog.result.Result)} handler The handler to be
  139. * called. The handler is passed the result value and the result as
  140. * parameters.
  141. * @param {T=} opt_scope Optional scope for the handler.
  142. * @template T
  143. */
  144. goog.result.waitOnSuccess = function(result, handler, opt_scope) {
  145. goog.result.wait(result, function(res) {
  146. if (res.getState() == goog.result.Result.State.SUCCESS) {
  147. // 'this' refers to opt_scope
  148. handler.call(this, res.getValue(), res);
  149. }
  150. }, opt_scope);
  151. };
  152. /**
  153. * Calls the handler if the result action errors. The result object is passed as
  154. * the only parameter to the handler. The call will be immediate if the result
  155. * object has already resolved to an error.
  156. *
  157. * Example:
  158. *
  159. * <pre>
  160. *
  161. * var result = xhr.get('testdata/xhr_test_text.data');
  162. *
  163. * // Attach a failure handler.
  164. * goog.result.waitOnError(result, function(error) {
  165. * // Failed asynchronous call!
  166. * });
  167. * </pre>
  168. *
  169. * @param {!goog.result.Result} result The result to install the handlers.
  170. * @param {function(this:T, ?, !goog.result.Result)} handler The handler to be
  171. * called. The handler is passed the error and the result object as
  172. * parameters.
  173. * @param {T=} opt_scope Optional scope for the handler.
  174. * @template T
  175. */
  176. goog.result.waitOnError = function(result, handler, opt_scope) {
  177. goog.result.wait(result, function(res) {
  178. if (res.getState() == goog.result.Result.State.ERROR) {
  179. // 'this' refers to opt_scope
  180. handler.call(this, res.getError(), res);
  181. }
  182. }, opt_scope);
  183. };
  184. /**
  185. * Given a result and a transform function, returns a new result whose value,
  186. * on success, will be the value of the given result after having been passed
  187. * through the transform function.
  188. *
  189. * If the given result is an error, the returned result is also an error and the
  190. * transform will not be called.
  191. *
  192. * Example:
  193. * <pre>
  194. *
  195. * var result = xhr.getJson('testdata/xhr_test_json.data');
  196. *
  197. * // Transform contents of returned data using 'processJson' and create a
  198. * // transformed result to use returned JSON.
  199. * var transformedResult = goog.result.transform(result, processJson);
  200. *
  201. * // Attach success and failure handlers to the transformed result.
  202. * goog.result.waitOnSuccess(transformedResult, function(resultValue, result) {
  203. * var jsonData = resultValue;
  204. * assertEquals('ok', jsonData['stat']);
  205. * });
  206. *
  207. * goog.result.waitOnError(transformedResult, function(error) {
  208. * // Failed getJson call
  209. * });
  210. * </pre>
  211. *
  212. * @param {!goog.result.Result} result The result whose value will be
  213. * transformed.
  214. * @param {function(?):?} transformer The transformer
  215. * function. The return value of this function will become the value of the
  216. * returned result.
  217. *
  218. * @return {!goog.result.DependentResult} A new Result whose eventual value will
  219. * be the returned value of the transformer function.
  220. */
  221. goog.result.transform = function(result, transformer) {
  222. var returnedResult = new goog.result.DependentResultImpl_([result]);
  223. goog.result.wait(result, function(res) {
  224. if (res.getState() == goog.result.Result.State.SUCCESS) {
  225. returnedResult.setValue(transformer(res.getValue()));
  226. } else {
  227. returnedResult.setError(res.getError());
  228. }
  229. });
  230. return returnedResult;
  231. };
  232. /**
  233. * The chain function aids in chaining of asynchronous Results. This provides a
  234. * convenience for use cases where asynchronous operations must happen serially
  235. * i.e. subsequent asynchronous operations are dependent on data returned by
  236. * prior asynchronous operations.
  237. *
  238. * It accepts a result and an action callback as arguments and returns a
  239. * result. The action callback is called when the first result succeeds and is
  240. * supposed to return a second result. The returned result is resolved when one
  241. * of both of the results resolve (depending on their success or failure.) The
  242. * state and value of the returned result in the various cases is documented
  243. * below:
  244. * <pre>
  245. *
  246. * First Result State: Second Result State: Returned Result State:
  247. * SUCCESS SUCCESS SUCCESS
  248. * SUCCESS ERROR ERROR
  249. * ERROR Not created ERROR
  250. * </pre>
  251. *
  252. * The value of the returned result, in the case both results succeed, is the
  253. * value of the second result (the result returned by the action callback.)
  254. *
  255. * Example:
  256. * <pre>
  257. *
  258. * var testDataResult = xhr.get('testdata/xhr_test_text.data');
  259. *
  260. * // Chain this result to perform another asynchronous operation when this
  261. * // Result is resolved.
  262. * var chainedResult = goog.result.chain(testDataResult,
  263. * function(testDataResult) {
  264. *
  265. * // The result value of testDataResult is the URL for JSON data.
  266. * var jsonDataUrl = testDataResult.getValue();
  267. *
  268. * // Create a new Result object when the original result is resolved.
  269. * var jsonResult = xhr.getJson(jsonDataUrl);
  270. *
  271. * // Return the newly created Result.
  272. * return jsonResult;
  273. * });
  274. *
  275. * // The chained result resolves to success when both results resolve to
  276. * // success.
  277. * goog.result.waitOnSuccess(chainedResult, function(resultValue, result) {
  278. *
  279. * // At this point, both results have succeeded and we can use the JSON
  280. * // data returned by the second asynchronous call.
  281. * var jsonData = resultValue;
  282. * assertEquals('ok', jsonData['stat']);
  283. * });
  284. *
  285. * // Attach the error handler to be called when either Result fails.
  286. * goog.result.waitOnError(chainedResult, function(result) {
  287. * alert('chained result failed!');
  288. * });
  289. * </pre>
  290. *
  291. * @param {!goog.result.Result} result The result to chain.
  292. * @param {function(this:T, !goog.result.Result):!goog.result.Result}
  293. * actionCallback The callback called when the result is resolved. This
  294. * callback must return a Result.
  295. * @param {T=} opt_scope Optional scope for the action callback.
  296. * @return {!goog.result.DependentResult} A result that is resolved when both
  297. * the given Result and the Result returned by the actionCallback have
  298. * resolved.
  299. * @template T
  300. */
  301. goog.result.chain = function(result, actionCallback, opt_scope) {
  302. var dependentResult = new goog.result.DependentResultImpl_([result]);
  303. // Wait for the first action.
  304. goog.result.wait(result, function(result) {
  305. if (result.getState() == goog.result.Result.State.SUCCESS) {
  306. // The first action succeeded. Chain the contingent action.
  307. var contingentResult = actionCallback.call(opt_scope, result);
  308. dependentResult.addParentResult(contingentResult);
  309. goog.result.wait(contingentResult, function(contingentResult) {
  310. // The contingent action completed. Set the dependent result based on
  311. // the contingent action's outcome.
  312. if (contingentResult.getState() == goog.result.Result.State.SUCCESS) {
  313. dependentResult.setValue(contingentResult.getValue());
  314. } else {
  315. dependentResult.setError(contingentResult.getError());
  316. }
  317. });
  318. } else {
  319. // First action failed, the dependent result should also fail.
  320. dependentResult.setError(result.getError());
  321. }
  322. });
  323. return dependentResult;
  324. };
  325. /**
  326. * Returns a result that waits on all given results to resolve. Once all have
  327. * resolved, the returned result will succeed (and never error).
  328. *
  329. * Example:
  330. * <pre>
  331. *
  332. * var result1 = xhr.get('testdata/xhr_test_text.data');
  333. *
  334. * // Get a second independent Result.
  335. * var result2 = xhr.getJson('testdata/xhr_test_json.data');
  336. *
  337. * // Create a Result that resolves when both prior results resolve.
  338. * var combinedResult = goog.result.combine(result1, result2);
  339. *
  340. * // Process data after resolution of both results.
  341. * goog.result.waitOnSuccess(combinedResult, function(results) {
  342. * goog.array.forEach(results, function(result) {
  343. * alert(result.getState());
  344. * });
  345. * });
  346. * </pre>
  347. *
  348. * @param {...!goog.result.Result} var_args The results to wait on.
  349. *
  350. * @return {!goog.result.DependentResult} A new Result whose eventual value will
  351. * be the resolved given Result objects.
  352. */
  353. goog.result.combine = function(var_args) {
  354. /** @type {!Array<!goog.result.Result>} */
  355. var results = goog.array.clone(arguments);
  356. var combinedResult = new goog.result.DependentResultImpl_(results);
  357. var isResolved = function(res) {
  358. return res.getState() != goog.result.Result.State.PENDING;
  359. };
  360. var checkResults = function() {
  361. if (combinedResult.getState() == goog.result.Result.State.PENDING &&
  362. goog.array.every(results, isResolved)) {
  363. combinedResult.setValue(results);
  364. }
  365. };
  366. goog.array.forEach(
  367. results, function(result) { goog.result.wait(result, checkResults); });
  368. return combinedResult;
  369. };
  370. /**
  371. * Returns a result that waits on all given results to resolve. Once all have
  372. * resolved, the returned result will succeed if and only if all given results
  373. * succeeded. Otherwise it will error.
  374. *
  375. * Example:
  376. * <pre>
  377. *
  378. * var result1 = xhr.get('testdata/xhr_test_text.data');
  379. *
  380. * // Get a second independent Result.
  381. * var result2 = xhr.getJson('testdata/xhr_test_json.data');
  382. *
  383. * // Create a Result that resolves when both prior results resolve.
  384. * var combinedResult = goog.result.combineOnSuccess(result1, result2);
  385. *
  386. * // Process data after successful resolution of both results.
  387. * goog.result.waitOnSuccess(combinedResult, function(results) {
  388. * var textData = results[0].getValue();
  389. * var jsonData = results[1].getValue();
  390. * assertEquals('Just some data.', textData);
  391. * assertEquals('ok', jsonData['stat']);
  392. * });
  393. *
  394. * // Handle errors when either or both results failed.
  395. * goog.result.waitOnError(combinedResult, function(combined) {
  396. * var results = combined.getError();
  397. *
  398. * if (results[0].getState() == goog.result.Result.State.ERROR) {
  399. * alert('result1 failed');
  400. * }
  401. *
  402. * if (results[1].getState() == goog.result.Result.State.ERROR) {
  403. * alert('result2 failed');
  404. * }
  405. * });
  406. * </pre>
  407. *
  408. * @param {...!goog.result.Result} var_args The results to wait on.
  409. *
  410. * @return {!goog.result.DependentResult} A new Result whose eventual value will
  411. * be an array of values of the given Result objects.
  412. */
  413. goog.result.combineOnSuccess = function(var_args) {
  414. var results = goog.array.clone(arguments);
  415. var combinedResult = new goog.result.DependentResultImpl_(results);
  416. var resolvedSuccessfully = function(res) {
  417. return res.getState() == goog.result.Result.State.SUCCESS;
  418. };
  419. goog.result.wait(
  420. goog.result.combine.apply(goog.result.combine, results),
  421. // The combined result never ERRORs
  422. function(res) {
  423. var results =
  424. /** @type {Array<!goog.result.Result>} */ (res.getValue());
  425. if (goog.array.every(results, resolvedSuccessfully)) {
  426. combinedResult.setValue(results);
  427. } else {
  428. combinedResult.setError(results);
  429. }
  430. });
  431. return combinedResult;
  432. };
  433. /**
  434. * Given a DependentResult, cancels the Results it depends on (that is, the
  435. * results returned by getParentResults). This function does not recurse,
  436. * so e.g. parents of parents are not canceled; only the immediate parents of
  437. * the given Result are canceled.
  438. *
  439. * Example using @see goog.result.combine:
  440. * <pre>
  441. * var result1 = xhr.get('testdata/xhr_test_text.data');
  442. *
  443. * // Get a second independent Result.
  444. * var result2 = xhr.getJson('testdata/xhr_test_json.data');
  445. *
  446. * // Create a Result that resolves when both prior results resolve.
  447. * var combinedResult = goog.result.combineOnSuccess(result1, result2);
  448. *
  449. * combinedResult.wait(function() {
  450. * if (combinedResult.isCanceled()) {
  451. * goog.result.cancelParentResults(combinedResult);
  452. * }
  453. * });
  454. *
  455. * // Now, canceling combinedResult will cancel both result1 and result2.
  456. * combinedResult.cancel();
  457. * </pre>
  458. * @param {!goog.result.DependentResult} dependentResult A Result that is
  459. * dependent on the values of other Results (for example the Result of a
  460. * goog.result.combine, goog.result.chain, or goog.result.transform call).
  461. * @return {boolean} True if any results were successfully canceled; otherwise
  462. * false.
  463. * TODO(user): Implement a recursive version of this that cancels all
  464. * ancestor results.
  465. */
  466. goog.result.cancelParentResults = function(dependentResult) {
  467. var anyCanceled = false;
  468. var results = dependentResult.getParentResults();
  469. for (var n = 0; n < results.length; n++) {
  470. anyCanceled |= results[n].cancel();
  471. }
  472. return !!anyCanceled;
  473. };
  474. /**
  475. * A DependentResult represents a Result whose eventual value depends on the
  476. * value of one or more other Results. For example, the Result returned by
  477. * @see goog.result.chain or @see goog.result.combine is dependent on the
  478. * Results given as arguments.
  479. *
  480. * @param {!Array<!goog.result.Result>} parentResults A list of Results that
  481. * will affect the eventual value of this Result.
  482. * @constructor
  483. * @implements {goog.result.DependentResult}
  484. * @extends {goog.result.SimpleResult}
  485. * @private
  486. */
  487. goog.result.DependentResultImpl_ = function(parentResults) {
  488. goog.result.DependentResultImpl_.base(this, 'constructor');
  489. /**
  490. * A list of Results that will affect the eventual value of this Result.
  491. * @type {!Array<!goog.result.Result>}
  492. * @private
  493. */
  494. this.parentResults_ = parentResults;
  495. };
  496. goog.inherits(goog.result.DependentResultImpl_, goog.result.SimpleResult);
  497. /**
  498. * Adds a Result to the list of Results that affect this one.
  499. * @param {!goog.result.Result} parentResult A result whose value affects the
  500. * value of this Result.
  501. */
  502. goog.result.DependentResultImpl_.prototype.addParentResult = function(
  503. parentResult) {
  504. this.parentResults_.push(parentResult);
  505. };
  506. /** @override */
  507. goog.result.DependentResultImpl_.prototype.getParentResults = function() {
  508. return this.parentResults_;
  509. };