Asynchronous Programming in JavaScript
There are different ways in JavaScript to create asynchronous code. The most important ones are the following:
- Callbacks
- Promises
- Async/Await
- RxJS Observables
Callbacks
This is the old-fashioned classical approach to asynchronous programming. You provide a function as an argument to another function that executes an asynchronous task. When the asynchronous task completes, the executing function calls your callback function.
The main disadvantage of this approach occurs when you have multiple chained asynchronous tasks, which requires you to define callback functions within callback functions within callback functions… This is called callback hell.
function greeting(name) {
console.log(`Hello ${name}!`);
}
function introduction(firstName, lastName, callback) {
const fullName = `${firstName} ${lastName}`;
callback(fullName);
}
introduction('Nouman','shah', greeting);
//"Hello Nouman shah!"
Promises
Promises have been introduced in ES6 (2015) to allow for more readable asynchronous code than is possible with callbacks.
The main difference between callbacks and promises is that with callbacks you tell the executing function what to do when the asynchronous task completes, whereas with promises the executing function returns a special object to you (the promise) and then you tell the promise what to do when the asynchronous task completes.
Promises have three states:
- Pending: This is the initial state of the Promise before an operation begins
- Fulfilled: This means the specified operation was completed
- Rejected: The operation did not complete; an error value is usually thrown
function getUsers(onSuccess) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Handle resolve and reject in the asynchronous API
if (onSuccess) {
resolve([
{id: 1, name: 'Jerry'},
{id: 2, name: 'Elaine'},
{id: 3, name: 'George'},
])
} else {
reject('Failed to fetch data!')
}
}, 1000)
})
}
// Run the getUsers function with the false flag to trigger an error
getUsers(false)
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error)
})
Async/Await
There’s a special syntax to work with promises in a more comfortable fashion, called “async/await”. It’s surprisingly easy to understand and use.
Basically, you can declare a function to be async, which allows you to use the await keyword in the body of this function. The await keyword can be put in front of an expression that evaluates to a promise. The await keyword pauses the execution of the async function until the promise is resolved. When this happens, the entire await expression evaluates to the result value of the promise, and then the execution of the async function resumes.
Furthermore, the async function itself returns a promise as well that is resolved when the execution of the function body completes.
function asyncTask(i) {
return new Promise(resolve => resolve(i + 1));
}
async function runAsyncTasks() {
const res1 = await asyncTask(0);
const res2 = await asyncTask(res1);
const res3 = await asyncTask(res2);
return "Everything done"
}
runAsyncTasks().then(result => console.log(result));
RxJS Observables
Observables are also like callbacks and promises that are responsible for handling async requests. Observables are a part of the RXJS library that makes use of Observables, making it really easy to write asynchronous code.
There are four stages through which observables pass. They are:
- Creation
- Subscription
- Execution
- Destruction
Creation of an observable is done using a create function.
var observable = Rx.Observable.create((observer: any) =>{
})
To make an observable work, we have to subscribe it. This can be done using the subscribe method.
observable.subscribe((data)=>{
console.log(data);
})
Execution of observables is what is inside of the create block.
Destruction after an error or a complete notification, the observable is automatically unsubscribed. But there are cases where we have to manually unsubscribe it. To manually do this task, just use:
var subscription = observable.subscribe(x => console.log(x)); // Later: subscription.unsubscribe();
Promises vs observables
Observables are lazy whereas promises are not
- Promises are eager: the executor function is called as soon as the promise is created.
- Observables are lazy: the subscriber function is only called when a client subscribes to the observable.
Observables handle multiple values unlike promises
Promises can only provide a single value whereas observables can give you multiple values.
Observables are cancelable
You can cancel observables by unsubscribing it using the unsubscribe method whereas promises don’t have such a feature.
Top comments (5)
I missed AsyncIterables in this post which are similar to Observables, part of the ECMASpec, and implemented in all major JavaScript runtimes. developer.mozilla.org/en-US/docs/W...
A good summary, though I'm missing AsyncIterables, which is already pretty close to observables; RxJS observables are based on them.
Thanks :)
Great post!
Thank you :)