Background
JavaScript is a single threaded language, which means it processes code synchronously. We can archive asynchronous behavior, using callbacks. Suppose, if we have a long running task, which requires 10 seconds to complete processing, then we can queue up this task for later, using Microtask queues (for more details, click here). This article is written for Knockout framework but is still worth reading to understand the concept of Microtask. JavaScript pulls these tasks from Microtask queue and start processing but it processes one task at a time.
Prerequisites
- Any IDE (I used Visual Studio).
- CDN link of Bootstrap for styling.
- CDN link of Angular
Callbacks
Suppose, we have an Application which contains an API to do some calculation but it requires 2 minutes to do this calculation. Then, we can wait for the user to do the calculation. Alternatively, we can add some callbacks, so that when the calculation completes, it will call the callbacks passed to API.
Code snippet
- function add(x, y, callback) {
- callback(x + y);
- }
- add(2, 6, function (result) {
- $scope.result = result;
- });
Output
Let’s add some delay in the add method as I am using $timeout Angular Service.
Code snippet
- function add(x, y, callback) {
- $timeout(function () {
- callback(x + y);
- }, 500)
- }
- let startTime = Date.now();
- add(2, 6, function (result) {
- $scope.result = result;
- $scope.totalTime = Date.now() - startTime;
- });
Output
In the output, we can clearly see that the total time to execute the add method is 501, which means it delayed works and calculation is delayed by 500 as we set the timeout value as 500 in the $timeout Service.
So far, everything looks good and we are happy with our code but the main mess will start when we need more methods to call inside call back, which will reduce the code readability.
Code snippet
- function add(x, y, callback) {
- $timeout(function () {
- callback(x + y);
- },500)
- }
- let startTime = Date.now();
- add(2, 6, function (result) {
- add(result, 3, function () {
- $scope.result = result;
- $scope.totalTime = Date.now() - startTime;
- })
- });
Output
In the code given above, we can clearly see that it takes twice the time compared to the previous attempt and results look proper but the main concern here is the code it stated looks messy. Now, suppose if we want to handle the errors, then the code will look, as shown below.
Code snippet
- function add(x, y, callback,errorCallback) {
- $timeout(function () {
- let result = x + y;
- if (result < 0)
- errorCallback("Negative error");
- else
- callback(result);
- }, 500)
- }
- let startTime = Date.now();
- add(2, 6, function (result) {
- add(result, -13, function () {
- $scope.result = result;
- $scope.totalTime = Date.now() - startTime;
- }, function (error) {
- $scope.result = error;
- $log.error(error);
- })
- }, function (error) {
- $log.error(error);
- });
Output
By looking at the output, we can see that our code is working properly but the code looks messy, tough to read and understand the code.
Promise
Promise is a way of writing asynchronous coding in a better understandable way. Callback way of writing makes the code very messy as we have seen previously. We can define Promises, using $timeout, $q (angular ways) and Promises (introduced in ES 2015).
Promise using $timeout
$timeout Service of Angular returns a Promise, which we can use it to do our basic Promise example.
Code snippet
- function add(x, y) {
- return $timeout(function () {
- return x + y;
- }, 500);
-
- startTime = Date.now();
- 5, 2)
- .then(function (result) {
- return add(result, 3);
- }).then(function (result) {
- return add(result, 3);
- }).then(function (result) {
- $scope.x = result;
- $scope.totalTime = Date.now() - startTime;
- });
Output
The code given above runs perfectly and total time proves that add method executes 3 times due to which total time is 1520, which means more than 3 times of the time; we set in timeout. Promise makes the code readable and easy to understand than compared to Callback approach.
Promise using ES2015
In ES2015, we can implement Promise feature with an instance of Promise. This shows the importance of Promise/ Asynchronous programming that ECMA standards also defined in the standard. Let’s consider an example of Promise, using ES2015.
Code snippet
- function add(x, y) {
- return new Promise(function (resolve, reject) {
- let result = x + y;
- if (result < 0) {
- reject("Negative Number");
- }
- else {
- resolve(result);
- }
- });
- }
-
- add(1, 3).then(function (result) {
- $log.debug(result);
- return add(result, -13);
- }, function (error) {
- $log.debug(error);
-
- })
- .then(function (result) {
- $log.debug(result);
- }, function (error) {
- $log.debug(error);
-
- })
Output
Promise using $q
$q is an Angular Service, which we can use to implement deferred/ Asynchronous coding. This is a very clean and readable way of writing asynchronous way of coding and we can also add caching to handle the errors at any stage and finally to write the logic, which is supposed to be run at the end of all calls. In the example given below, we continue with the same add method, using $q Service.
First, we will execute a positive test scenario.
Code snippet
- function add(x, y) {
- let q = $q.defer();
- $timeout(function () {
- let result = x + y;
- if (result < 0) {
- q.reject("Negative error");
- }
- else
- q.resolve(result);
- }, 500);
- return q.promise;
- };
- let startTime = Date.now();
- add(5, 2)
- .then(function (result) {
- return add(result,13);
- }).then(function (result) {
- return add(result, 3);
- }).then(function (result) {
- $scope.result = result;
- }).catch(function () {
- $scope.result = "Error";
- }).finally(function () {
- $scope.totalTime = Date.now() - startTime;
- });
Output
Now, we will execute a negative scenario, which will execute the code in cache block and finally block executes in both the cases.
Code Snippet
- function add(x, y) {
- let q = $q.defer();
- $timeout(function () {
- let result = x + y;
- if (result < 0) {
- q.reject("Negative error");
- }
- else
- q.resolve(result);
- }, 500);
- return q.promise;
- };
- let startTime = Date.now();
- add(5, 2)
- .then(function (result) {
- return add(result,-13);
- }).then(function (result) {
- return add(result, 3);
- }).then(function (result) {
- $scope.result = result;
- }).catch(function () {
- $scope.result = "Error";
- }).finally(function () {
- $scope.totalTime = Date.now() - startTime;
- });
Output
Conclusion
In this, we have discussed different ways of writing asynchronous code, starting from the Callback approach, which makes code messy, then using $timeout and $q (in Angular) and Promise with ES2015, using these approaches, we can have a better readable code also. We can implement caching and finally, I make our code cleaner.
References
- http://exploringjs.com/es6/ch_promises.html#sec_introduction-promises
- https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://docs.angularjs.org/api/ng/service/$q
- https://www.w3schools.com/jquery/jquery_callback.asp