Code


<!DOCTYPE html> <html> <head> <script src="angular.js"></script> <script src="script.js"></script> </head> <body ng-app="mainModule"> <div ng-controller="mainController"> <h3>1. Basic example</h3> <button ng-click="basicAsyncFunc(true)">Call asynchronous function with SUCCESS</button><br /> <button ng-click="basicAsyncFunc(false)">Call asynchronous function with ERROR</button><br /> <br /> <strong>Function result: </strong>{{basicAsyncFuncResult}}<br /> <br /> <h3>2. Registering multiple callbacks</h3> <button ng-click="multiCb()">Call asynchronous function</button><br /> <br /> <strong>Result 1: </strong>{{multiCbResult1}}<br /> <strong>Result 2: </strong>{{multiCbResult2}}<br /> <br /> <h3>3. Alternative callback registration methods</h3> <button ng-click="altBasicAsyncFunc(true)">Call asynchronous function with SUCCESS</button><br /> <button ng-click="altBasicAsyncFunc(false)">Call asynchronous function with ERROR</button><br /> <br /> <strong>Function result: </strong>{{altBasicAsyncFuncResult}}<br /> <br /> <h3>4. Chaining promises</h3> Both asyncFunc1 and asyncFunc2 are successful<br /> <button ng-click="chainedAsyncFunc([true, true])">Execution 1</button><br /> <br /> asyncFunc1 is succesful but asyncFunc2 fails<br /> <button ng-click="chainedAsyncFunc([true, false])">Execution 2</button><br /> <br /> asyncFunc1 fails, asyncFunc2 would be successful but it's never called<br /> <button ng-click="chainedAsyncFunc([false, true])">Execution 3</button><br /> <br /> asyncFunc1 fails, asyncFunc2 would fail as well but it's never called<br /> <button ng-click="chainedAsyncFunc([false, false])">Execution 4</button><br /> <br /> <strong>Input: </strong>{{chainedAsyncFuncInput}}<br /> <strong>Output: </strong>{{chainedAsyncFuncResult}}<br /> <br /> <h3>5. Aggregating promises</h3> Calls 1, 2 and 3 are successful<br /> <button ng-click="aggregatedAsyncFunc(true, true, true)">Execution 1</button><br /> <br /> Calls 1 and 3 are successful while 2 fails<br /> <button ng-click="aggregatedAsyncFunc(true, false, true)">Execution 2</button><br /> <br /> Calls 1, 2 and 3 fail<br /> <button ng-click="aggregatedAsyncFunc(false, false, false)">Execution 3</button><br /> <br /> <strong>Input: </strong>{{aggregatedAsyncFuncInput}}<br /> <strong>Output: </strong>{{aggregatedAsyncFuncResult}}<br /> <br /> <h3>6. Wrapping a value in a promise</h3> Calls 1 and 2 are successful<br /> <button ng-click="whenAsyncFunc(true, true)">Execution 1</button><br /> <br /> Call 1 is successful while 2 fails<br /> <button ng-click="whenAsyncFunc(true, false)">Execution 2</button><br /> <br /> <strong>Input: </strong>{{whenAsyncFuncInput}}<br /> <strong>Output: </strong>{{whenAsyncFuncResult}}<br /> <br /> <h3>7. Aggregating promises returned by the $http service</h3> Calls 1, 2 and 3 are successful<br /> <button ng-click="aggregatedHTTPAsyncFunc(true, true, true)">Execution 1</button><br /> <br /> Calls 1 and 3 are successful while 2 fails<br /> <button ng-click="aggregatedHTTPAsyncFunc(true, false, true)">Execution 2</button><br /> <br /> Calls 1, 2 and 3 fail<br /> <button ng-click="aggregatedHTTPAsyncFunc(false, false, false)">Execution 3</button><br /> <br /> <strong>Input: </strong>{{aggregatedHTTPAsyncFuncInput}}<br /> <strong>Output: </strong>{{aggregatedHTTPAsyncFuncResult}}<br /> <br /> </div> </body> </html>
angular.module("mainModule", []) .controller("mainController", function ($scope, $q, $http) { var customAsyncFunc = function (callerId, withSuccess, duration) { // Default values for the optional arguments duration = (typeof duration === "undefined") ? 3000 : duration; // Duration must be at least 3 seconds if (duration < 3000) duration = 3000; var deferred = $q.defer(); var computingStep = 1; var computationFunc = window.setInterval(function () { deferred.notify("computing step " + (computingStep++) + "..."); }, 1000); window.setTimeout(function() { if (withSuccess) { clearInterval(computationFunc); deferred.resolve(callerId + " said: successful execution!"); } else { clearInterval(computationFunc); deferred.reject(callerId + " said: function failure!"); } }, duration); return deferred.promise; }; $scope.basicAsyncFunc = function (withSuccess) { var promise = customAsyncFunc("The basicAsyncFunc", withSuccess); $scope.basicAsyncFuncResult = "Waiting for the result..."; promise.then( // Success handler function (successMessage) { $scope.basicAsyncFuncResult = successMessage; }, // Failure handler function (failureMessage) { $scope.basicAsyncFuncResult = failureMessage; }, // Update handler function (updateMessage) { $scope.basicAsyncFuncResult = updateMessage; } ); }; $scope.multiCb = function () { var HandlerObject = function (handlerId, updatedVarName) { this.successHandler = function (successMessage) { $scope[updatedVarName] = "[" + handlerId + "] " + successMessage; }; this.failureHandler = function (failureMessage) { $scope[updatedVarName] = "[" + handlerId + "] " + failureMessage; }; this.updateHandler = function (updateMessage) { $scope[updatedVarName] = "[" + handlerId + "] " + updateMessage; }; }; var handler1 = new HandlerObject(1, "multiCbResult1"); var handler2 = new HandlerObject(2, "multiCbResult2"); var promise = customAsyncFunc("multiCb", true); promise.then(handler1.successHandler, handler1.failureHandler, handler1.updateHandler); promise.then(handler2.successHandler, handler2.failureHandler, handler2.updateHandler); }; $scope.altBasicAsyncFunc = function (withSuccess) { var promise = customAsyncFunc("The altBasicAsyncFunc", withSuccess); $scope.altBasicAsyncFuncResult = "Waiting for the result..."; promise.then( // Success handler function (successMessage) { $scope.altBasicAsyncFuncResult = successMessage; }, null, // Update handler function (updateMessage) { $scope.altBasicAsyncFuncResult = updateMessage; } ); promise.catch( // Failure handler function (failureMessage) { $scope.altBasicAsyncFuncResult = failureMessage; } ); promise.finally( // Finalization handler function () { $scope.altBasicAsyncFuncResult += " (end of execution)"; } ); }; $scope.chainedAsyncFunc = function (executionPath) { var asyncFunc1 = function (executionPath) { var deferred = $q.defer(); if (executionPath[0]) { executionPath[0] = "SUCCESS"; deferred.resolve(executionPath); } else { deferred.reject("asyncFunc1 failure [" + executionPath + "]"); } return deferred.promise; }; var asyncFunc2 = function (executionPath) { var deferred = $q.defer(); if (executionPath[1]) { executionPath[1] = "SUCCESS"; deferred.resolve(executionPath); } else { deferred.reject("asyncFunc2 failure [" + executionPath + "]"); } return deferred.promise; }; var logExecutionResult = function (message) { $scope.chainedAsyncFuncResult = message; return message; }; $scope.chainedAsyncFuncInput = angular.copy(executionPath); asyncFunc1(executionPath) .then(asyncFunc2, logExecutionResult) .then(logExecutionResult, logExecutionResult); }; $scope.aggregatedAsyncFunc = function (call1Success, call2Success, call3Success) { $scope.aggregatedAsyncFuncInput = "[" + call1Success + ", " + call2Success + ", " + call3Success + "]"; var promise = $q.all([ customAsyncFunc("Call 1", call1Success, 5000), customAsyncFunc("Call 2", call2Success, 4000), customAsyncFunc("Call 3", call3Success, 3000) ]); $scope.aggregatedAsyncFuncResult = "Waiting for all the results..."; promise.then( // Success handler function (successMessage) { $scope.aggregatedAsyncFuncResult = successMessage; }, // Failure handler function (failureMessage) { $scope.aggregatedAsyncFuncResult = failureMessage; } ); }; $scope.whenAsyncFunc = function (call1Success, call2Success) { $scope.whenAsyncFuncInput = "[" + call1Success + ", " + call2Success + "]"; var promise = $q.all([ $q.when("Already resolved promise"), customAsyncFunc("Call 1", call1Success, 3000), customAsyncFunc("Call 2", call2Success, 4000) ]); $scope.whenAsyncFuncResult = "Waiting for all the results..."; promise.then( // Success handler function (successMessage) { $scope.whenAsyncFuncResult = successMessage; }, // Failure handler function (failureMessage) { $scope.whenAsyncFuncResult = failureMessage; } ); }; $scope.aggregatedHTTPAsyncFunc = function (call1Success, call2Success, call3Success) { var getCallParams = function (callID, duration) { var params = { callID: callID, sleep: duration }; var config = { params: params }; return config; }; $scope.aggregatedHTTPAsyncFuncInput = "[" + call1Success + ", " + call2Success + ", " + call3Success + "]"; var promise = $q.all([ $http.get((call1Success ? "server.php" : "invalid-url.php"), getCallParams("Call1", 5)), $http.get((call2Success ? "server.php" : "invalid-url.php"), getCallParams("Call2", 4)), $http.get((call3Success ? "server.php" : "invalid-url.php"), getCallParams("Call3", 3)) ]); $scope.aggregatedHTTPAsyncFuncResult = "Waiting for all the results..."; promise.then( // Success handler function (httpResultArray) { $scope.aggregatedHTTPAsyncFuncResult = "[" + httpResultArray[0].data + ", " + httpResultArray[1].data + ", " + httpResultArray[2].data + "]"; }, // Failure handler function (httpResult) { $scope.aggregatedHTTPAsyncFuncResult = httpResult.status; } ); }; });
<?php if ($_SERVER["REQUEST_METHOD"] === "GET") { if (isset($_GET["sleep"])) { sleep($_GET["sleep"]); } $result = ""; if (isset($_GET["callID"])) { $result = $_GET["callID"] . " "; } $result .= "GET request successful"; echo $result; } ?>

Example


Description


The $q service included in AngularJS provides an API to deal with asynchronous functions and gives a way to synchronize them.

Let's talk first about the customAsyncFunc function that we've defined in our script file. The basic idea behind it is to have a function that simulates the execution of an asynchronous task and notifies about the progress during the execution and about the final result. The function simulates the execution of 2 steps taking 1 second each and then, after a custom total duration, notifies about the final result with a successful execution or a failure. The custom duration must be at least 3 seconds to allow the steps to have enough time to complete. The $q service provides the defer method that returns a deferred object. We can see it as the representation of a task that will be completed in the future. The deferred object exposes the resolve, reject and notify methods that allow us to respectively notify that the task has completed, that it failed or that there has been some progress in the execution. Each one of these methods allows to pass a value (a string, a number or any object) back to the listeners of the execution events. The customAsyncFunc function returns a promise object which can be seen as a placeholder for the result that will be available at the end of the asynchronous task execution. The promise object exposes some methods to allow the interested listeners to register some handlers that will notify them about the execution progress and final result.

In point 1 of the example, we see the basic structure of a call to an asynchronous function and the registration of the event handlers to be notified about its execution. We can invoke the then method of the promise returned by the asynchronous function to register a success, a failure and an update handler. Each one of them takes a single parameter with the value sent by the asynchronous function. The success handler takes the value passed to the resolve method of the deferred object, the failure handler takes the value of the reject method and the update handler takes the value of the notify method. When the customAsyncFunc function is called we have the choice of simulating a success or a failure to see the different outcome.

In point 2 we see that we can register multiple callbacks to the same promise and be notified about the same events in all the provided handler functions.

Point 3 shows alternative ways of registering our handlers. The then method of the promise registers only the success and update handlers while it doesn't register the failure one because we show that we can register it with the catch method (it is just a shortcut for then(null, failureHandler, null)). If we wanted to just listen to the success event, we could have just written then(successHandler) without specifying the other parameters. Here we also have the finally method that is useful to register a finalization handler that will be called at the end of the execution both in case of success or failure (the finalization handler is called after the registered success and failure handlers).

Having to wait for an asynchronous function to end before the next one starts is a typical need when the output of the first function is needed as input of the second. Point 4 of the example shows how we can chain multiple asynchronous functions by chaining their promises. Here we have two functions, asyncFunc1 and asyncFunc2. They take an array as input, the executionPath array, just to make it easy to specify which function we want to be successful or fail (the first element of the array tells the outcome we want from asyncFunc1, while the second one tells the outcome of asyncFunc2). When asyncFunc1 is called, it takes executionPath as input and if successful sets the first element of the array as SUCCESS and passes executionPath to the next function by returning it as a result with the resolve method. asyncFunc2 acts as success handler of asyncFunc1 and receives as input asyncFunc1's output. The logExecutionResult is useful in any case we just want to log an asynchronous call outcome (it acts as a generic handler). If asyncFunc1 fails, the logExecutionResult is called twice, the first time as a failure handler of asyncFunc1 and second time as a failure handler of asyncFunc2 because the error is propagated to all the failure handlers until the last one (the value returned by logExecutionResult is passed as input of the next failure handler).

If we need to wait for multiple asynchronous functions to end and want to be notified when all of them ended, we can use the all method of the $q service. Point 5 of the example shows that we can call 3 asynchronous functions with a different execution duration and have a single success and failure handler. When a function fails, the failure handler is called with the value returned by the reject method called by the function that failed first. When all the asynchronous functions are successful, the success handler receives as input an array containing all the success values of all the functions where every element in the array corresponds to the success value of the function in the same position in the array passed to the all method.

Point 6 is like point 5, just with a little difference. The $q service provides the when method that is useful to wrap any value in a promise. We might need this if the want to mix promises from asynchronous functions with already known values. The when method creates an already resolved promise.

In point 7 we see that the $q service can be very useful also to manage asynchronous calls made to a server through the $http service. In fact, the methods provided by the $http service return promises augmented with other properties and methods, but they are still promises and so they can be used with the $q service. Here we see that we can wait for 3 different GET server calls that take a different time to complete.