Path: blob/trunk/third_party/closure/goog/result/resultutil.js
2868 views
// Copyright 2012 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview This file provides primitives and tools (wait, transform,16* chain, combine) that make it easier to work with Results. This section17* gives an overview of their functionality along with some examples and the18* actual definitions have detailed descriptions next to them.19*20*21* NOTE: goog.result is soft deprecated - we expect to replace this and22* goog.async.Deferred with a wrapper around W3C Promises:23* http://dom.spec.whatwg.org/#promises.24*/2526goog.provide('goog.result');2728goog.require('goog.array');29goog.require('goog.result.DependentResult');30goog.require('goog.result.Result');31goog.require('goog.result.SimpleResult');323334/**35* Returns a successful result containing the provided value.36*37* Example:38* <pre>39*40* var value = 'some-value';41* var result = goog.result.immediateResult(value);42* assertEquals(goog.result.Result.State.SUCCESS, result.getState());43* assertEquals(value, result.getValue());44*45* </pre>46*47* @param {*} value The value of the result.48* @return {!goog.result.Result} A Result object that has already been resolved49* to the supplied value.50*/51goog.result.successfulResult = function(value) {52var result = new goog.result.SimpleResult();53result.setValue(value);54return result;55};565758/**59* Returns a failed result with the optional error slug set.60*61* Example:62* <pre>63*64* var error = new Error('something-failed');65* var result = goog.result.failedResult(error);66* assertEquals(goog.result.Result.State.ERROR, result.getState());67* assertEquals(error, result.getError());68*69* </pre>70*71* @param {*=} opt_error The error to which the result should resolve.72* @return {!goog.result.Result} A Result object that has already been resolved73* to the supplied Error.74*/75goog.result.failedResult = function(opt_error) {76var result = new goog.result.SimpleResult();77result.setError(opt_error);78return result;79};808182/**83* Returns a canceled result.84* The result will be resolved to an error of type CancelError.85*86* Example:87* <pre>88*89* var result = goog.result.canceledResult();90* assertEquals(goog.result.Result.State.ERROR, result.getState());91* var error = result.getError();92* assertTrue(error instanceof goog.result.Result.CancelError);93*94* </pre>95*96* @return {!goog.result.Result} A canceled Result.97*/98goog.result.canceledResult = function() {99var result = new goog.result.SimpleResult();100result.cancel();101return result;102};103104105/**106* Calls the handler on resolution of the result (success or failure).107* The handler is passed the result object as the only parameter. The call will108* be immediate if the result is no longer pending.109*110* Example:111* <pre>112*113* var result = xhr.get('testdata/xhr_test_text.data');114*115* // Wait for the result to be resolved and alert it's state.116* goog.result.wait(result, function(result) {117* alert('State: ' + result.getState());118* });119* </pre>120*121* @param {!goog.result.Result} result The result to install the handlers.122* @param {function(this:T, !goog.result.Result)} handler The handler to be123* called. The handler is passed the result object as the only parameter.124* @param {T=} opt_scope Optional scope for the handler.125* @template T126*/127goog.result.wait = function(result, handler, opt_scope) {128result.wait(handler, opt_scope);129};130131132/**133* Calls the handler if the result succeeds. The result object is the only134* parameter passed to the handler. The call will be immediate if the result135* has already succeeded.136*137* Example:138* <pre>139*140* var result = xhr.get('testdata/xhr_test_text.data');141*142* // attach a success handler.143* goog.result.waitOnSuccess(result, function(resultValue, result) {144* var datavalue = result.getvalue();145* alert('value: ' + datavalue + ' == ' + resultValue);146* });147* </pre>148*149* @param {!goog.result.Result} result The result to install the handlers.150* @param {function(this:T, ?, !goog.result.Result)} handler The handler to be151* called. The handler is passed the result value and the result as152* parameters.153* @param {T=} opt_scope Optional scope for the handler.154* @template T155*/156goog.result.waitOnSuccess = function(result, handler, opt_scope) {157goog.result.wait(result, function(res) {158if (res.getState() == goog.result.Result.State.SUCCESS) {159// 'this' refers to opt_scope160handler.call(this, res.getValue(), res);161}162}, opt_scope);163};164165166/**167* Calls the handler if the result action errors. The result object is passed as168* the only parameter to the handler. The call will be immediate if the result169* object has already resolved to an error.170*171* Example:172*173* <pre>174*175* var result = xhr.get('testdata/xhr_test_text.data');176*177* // Attach a failure handler.178* goog.result.waitOnError(result, function(error) {179* // Failed asynchronous call!180* });181* </pre>182*183* @param {!goog.result.Result} result The result to install the handlers.184* @param {function(this:T, ?, !goog.result.Result)} handler The handler to be185* called. The handler is passed the error and the result object as186* parameters.187* @param {T=} opt_scope Optional scope for the handler.188* @template T189*/190goog.result.waitOnError = function(result, handler, opt_scope) {191goog.result.wait(result, function(res) {192if (res.getState() == goog.result.Result.State.ERROR) {193// 'this' refers to opt_scope194handler.call(this, res.getError(), res);195}196}, opt_scope);197};198199200/**201* Given a result and a transform function, returns a new result whose value,202* on success, will be the value of the given result after having been passed203* through the transform function.204*205* If the given result is an error, the returned result is also an error and the206* transform will not be called.207*208* Example:209* <pre>210*211* var result = xhr.getJson('testdata/xhr_test_json.data');212*213* // Transform contents of returned data using 'processJson' and create a214* // transformed result to use returned JSON.215* var transformedResult = goog.result.transform(result, processJson);216*217* // Attach success and failure handlers to the transformed result.218* goog.result.waitOnSuccess(transformedResult, function(resultValue, result) {219* var jsonData = resultValue;220* assertEquals('ok', jsonData['stat']);221* });222*223* goog.result.waitOnError(transformedResult, function(error) {224* // Failed getJson call225* });226* </pre>227*228* @param {!goog.result.Result} result The result whose value will be229* transformed.230* @param {function(?):?} transformer The transformer231* function. The return value of this function will become the value of the232* returned result.233*234* @return {!goog.result.DependentResult} A new Result whose eventual value will235* be the returned value of the transformer function.236*/237goog.result.transform = function(result, transformer) {238var returnedResult = new goog.result.DependentResultImpl_([result]);239240goog.result.wait(result, function(res) {241if (res.getState() == goog.result.Result.State.SUCCESS) {242returnedResult.setValue(transformer(res.getValue()));243} else {244returnedResult.setError(res.getError());245}246});247248return returnedResult;249};250251252/**253* The chain function aids in chaining of asynchronous Results. This provides a254* convenience for use cases where asynchronous operations must happen serially255* i.e. subsequent asynchronous operations are dependent on data returned by256* prior asynchronous operations.257*258* It accepts a result and an action callback as arguments and returns a259* result. The action callback is called when the first result succeeds and is260* supposed to return a second result. The returned result is resolved when one261* of both of the results resolve (depending on their success or failure.) The262* state and value of the returned result in the various cases is documented263* below:264* <pre>265*266* First Result State: Second Result State: Returned Result State:267* SUCCESS SUCCESS SUCCESS268* SUCCESS ERROR ERROR269* ERROR Not created ERROR270* </pre>271*272* The value of the returned result, in the case both results succeed, is the273* value of the second result (the result returned by the action callback.)274*275* Example:276* <pre>277*278* var testDataResult = xhr.get('testdata/xhr_test_text.data');279*280* // Chain this result to perform another asynchronous operation when this281* // Result is resolved.282* var chainedResult = goog.result.chain(testDataResult,283* function(testDataResult) {284*285* // The result value of testDataResult is the URL for JSON data.286* var jsonDataUrl = testDataResult.getValue();287*288* // Create a new Result object when the original result is resolved.289* var jsonResult = xhr.getJson(jsonDataUrl);290*291* // Return the newly created Result.292* return jsonResult;293* });294*295* // The chained result resolves to success when both results resolve to296* // success.297* goog.result.waitOnSuccess(chainedResult, function(resultValue, result) {298*299* // At this point, both results have succeeded and we can use the JSON300* // data returned by the second asynchronous call.301* var jsonData = resultValue;302* assertEquals('ok', jsonData['stat']);303* });304*305* // Attach the error handler to be called when either Result fails.306* goog.result.waitOnError(chainedResult, function(result) {307* alert('chained result failed!');308* });309* </pre>310*311* @param {!goog.result.Result} result The result to chain.312* @param {function(this:T, !goog.result.Result):!goog.result.Result}313* actionCallback The callback called when the result is resolved. This314* callback must return a Result.315* @param {T=} opt_scope Optional scope for the action callback.316* @return {!goog.result.DependentResult} A result that is resolved when both317* the given Result and the Result returned by the actionCallback have318* resolved.319* @template T320*/321goog.result.chain = function(result, actionCallback, opt_scope) {322var dependentResult = new goog.result.DependentResultImpl_([result]);323324// Wait for the first action.325goog.result.wait(result, function(result) {326if (result.getState() == goog.result.Result.State.SUCCESS) {327// The first action succeeded. Chain the contingent action.328var contingentResult = actionCallback.call(opt_scope, result);329dependentResult.addParentResult(contingentResult);330goog.result.wait(contingentResult, function(contingentResult) {331332// The contingent action completed. Set the dependent result based on333// the contingent action's outcome.334if (contingentResult.getState() == goog.result.Result.State.SUCCESS) {335dependentResult.setValue(contingentResult.getValue());336} else {337dependentResult.setError(contingentResult.getError());338}339});340} else {341// First action failed, the dependent result should also fail.342dependentResult.setError(result.getError());343}344});345346return dependentResult;347};348349350/**351* Returns a result that waits on all given results to resolve. Once all have352* resolved, the returned result will succeed (and never error).353*354* Example:355* <pre>356*357* var result1 = xhr.get('testdata/xhr_test_text.data');358*359* // Get a second independent Result.360* var result2 = xhr.getJson('testdata/xhr_test_json.data');361*362* // Create a Result that resolves when both prior results resolve.363* var combinedResult = goog.result.combine(result1, result2);364*365* // Process data after resolution of both results.366* goog.result.waitOnSuccess(combinedResult, function(results) {367* goog.array.forEach(results, function(result) {368* alert(result.getState());369* });370* });371* </pre>372*373* @param {...!goog.result.Result} var_args The results to wait on.374*375* @return {!goog.result.DependentResult} A new Result whose eventual value will376* be the resolved given Result objects.377*/378goog.result.combine = function(var_args) {379/** @type {!Array<!goog.result.Result>} */380var results = goog.array.clone(arguments);381var combinedResult = new goog.result.DependentResultImpl_(results);382383var isResolved = function(res) {384return res.getState() != goog.result.Result.State.PENDING;385};386387var checkResults = function() {388if (combinedResult.getState() == goog.result.Result.State.PENDING &&389goog.array.every(results, isResolved)) {390combinedResult.setValue(results);391}392};393394goog.array.forEach(395results, function(result) { goog.result.wait(result, checkResults); });396397return combinedResult;398};399400401/**402* Returns a result that waits on all given results to resolve. Once all have403* resolved, the returned result will succeed if and only if all given results404* succeeded. Otherwise it will error.405*406* Example:407* <pre>408*409* var result1 = xhr.get('testdata/xhr_test_text.data');410*411* // Get a second independent Result.412* var result2 = xhr.getJson('testdata/xhr_test_json.data');413*414* // Create a Result that resolves when both prior results resolve.415* var combinedResult = goog.result.combineOnSuccess(result1, result2);416*417* // Process data after successful resolution of both results.418* goog.result.waitOnSuccess(combinedResult, function(results) {419* var textData = results[0].getValue();420* var jsonData = results[1].getValue();421* assertEquals('Just some data.', textData);422* assertEquals('ok', jsonData['stat']);423* });424*425* // Handle errors when either or both results failed.426* goog.result.waitOnError(combinedResult, function(combined) {427* var results = combined.getError();428*429* if (results[0].getState() == goog.result.Result.State.ERROR) {430* alert('result1 failed');431* }432*433* if (results[1].getState() == goog.result.Result.State.ERROR) {434* alert('result2 failed');435* }436* });437* </pre>438*439* @param {...!goog.result.Result} var_args The results to wait on.440*441* @return {!goog.result.DependentResult} A new Result whose eventual value will442* be an array of values of the given Result objects.443*/444goog.result.combineOnSuccess = function(var_args) {445var results = goog.array.clone(arguments);446var combinedResult = new goog.result.DependentResultImpl_(results);447448var resolvedSuccessfully = function(res) {449return res.getState() == goog.result.Result.State.SUCCESS;450};451452goog.result.wait(453goog.result.combine.apply(goog.result.combine, results),454// The combined result never ERRORs455function(res) {456var results =457/** @type {Array<!goog.result.Result>} */ (res.getValue());458if (goog.array.every(results, resolvedSuccessfully)) {459combinedResult.setValue(results);460} else {461combinedResult.setError(results);462}463});464465return combinedResult;466};467468469/**470* Given a DependentResult, cancels the Results it depends on (that is, the471* results returned by getParentResults). This function does not recurse,472* so e.g. parents of parents are not canceled; only the immediate parents of473* the given Result are canceled.474*475* Example using @see goog.result.combine:476* <pre>477* var result1 = xhr.get('testdata/xhr_test_text.data');478*479* // Get a second independent Result.480* var result2 = xhr.getJson('testdata/xhr_test_json.data');481*482* // Create a Result that resolves when both prior results resolve.483* var combinedResult = goog.result.combineOnSuccess(result1, result2);484*485* combinedResult.wait(function() {486* if (combinedResult.isCanceled()) {487* goog.result.cancelParentResults(combinedResult);488* }489* });490*491* // Now, canceling combinedResult will cancel both result1 and result2.492* combinedResult.cancel();493* </pre>494* @param {!goog.result.DependentResult} dependentResult A Result that is495* dependent on the values of other Results (for example the Result of a496* goog.result.combine, goog.result.chain, or goog.result.transform call).497* @return {boolean} True if any results were successfully canceled; otherwise498* false.499* TODO(user): Implement a recursive version of this that cancels all500* ancestor results.501*/502goog.result.cancelParentResults = function(dependentResult) {503var anyCanceled = false;504var results = dependentResult.getParentResults();505for (var n = 0; n < results.length; n++) {506anyCanceled |= results[n].cancel();507}508return !!anyCanceled;509};510511512513/**514* A DependentResult represents a Result whose eventual value depends on the515* value of one or more other Results. For example, the Result returned by516* @see goog.result.chain or @see goog.result.combine is dependent on the517* Results given as arguments.518*519* @param {!Array<!goog.result.Result>} parentResults A list of Results that520* will affect the eventual value of this Result.521* @constructor522* @implements {goog.result.DependentResult}523* @extends {goog.result.SimpleResult}524* @private525*/526goog.result.DependentResultImpl_ = function(parentResults) {527goog.result.DependentResultImpl_.base(this, 'constructor');528/**529* A list of Results that will affect the eventual value of this Result.530* @type {!Array<!goog.result.Result>}531* @private532*/533this.parentResults_ = parentResults;534};535goog.inherits(goog.result.DependentResultImpl_, goog.result.SimpleResult);536537538/**539* Adds a Result to the list of Results that affect this one.540* @param {!goog.result.Result} parentResult A result whose value affects the541* value of this Result.542*/543goog.result.DependentResultImpl_.prototype.addParentResult = function(544parentResult) {545this.parentResults_.push(parentResult);546};547548549/** @override */550goog.result.DependentResultImpl_.prototype.getParentResults = function() {551return this.parentResults_;552};553554555