Let us dive deeper into callbacks, promises and async/await :-
A Brief Overview about Callbacks and problems associated with using nested Callbacks
We all know, a callback is a function that is to be executed after another function has finished executing. Callbacks are good for simple cases.
But once the project requirements start to swell, we will see the piling layers of nested callbacks, which is also known as Callback Hell. So it is recommended to avoid deeply-nested callbacks. To solve this problem, the concept of Promises has been introduced in ES6.
Promises were the next logical step in escaping callback hell. This method did not remove the use of callbacks, but it made the chaining of functions straightforward and simplified the code, making it much easier to read.
A promise has to be in one of the three states mentioned below:-
- pending: the promise’s outcome hasn’t yet been determined because the asynchronous operation that will produce its result hasn’t completed yet.
- fulfilled: the asynchronous operation has completed, and the promise has a value.
- rejected: the asynchronous operation failed, and the promise will never be fulfilled. In the rejected state, a promise has a reason that indicates why the operation failed.
When a promise is pending, it can transition to the fulfilled or rejected state. Once a promise is fulfilled or rejected, however, it will never transition to any other state, and its value or failure reason will not change.
While using Promise approach we can
- Flatten callbacks
- Return values from asynchronous function
- Throw and Catch exceptions
In the above example of Promise approach func1() should return a Promise Object. This Promise object has two methods, “then” and “catch”. These methods will later be called depending on the state (fulfilled || rejected) of the Promise Object.
Promises gave us an easier way to deal with asynchrony in our code in sequential manner with the concept of async/await functions which has been introduced in ES8. Let us look into async/await functions with more clarity.
Async/await functions help us even more in allowing us to write completely synchronous looking code while performing asynchronous tasks behind the scenes.
Let’s use the async keyword. It can be placed before a function, like this:
We could explicitly return a promise, which would be the same:
So, async ensures that the function returns a promise, and wraps non-promises in it. There is another keyword, await.
Here’s an example with a promise that resolves in 2 seconds:
await and parallelism:-
When we need to wait for multiple promises, we can wrap them in Promise.all and then await:
The promise may take some time before it rejects. In that case there will be a delay before await throws an error. We can catch that error using try and catch, the same way as a regular throw:-
If we don’t have try and catch, then the promise generated by the call of the async function f() becomes rejected. We can append .catch to handle it: