Asynchronous Programming is the technique that enables us to handle asynchronous tasks like file uploads or HTTP requests which makes sure that our program remains responsive rather than just waiting for these tasks to complete.
Here I am going to talk about callbacks, promises and async-await in javascript. Note that this is intended for people with a basic understanding of javascript and not intended to be an ‘introduction’ to these topics. I would suggest learning about asynchronous programming at :
Before starting, the first thing we should note is that when we use these asynchronous functions in the real world, we typically deal with two outcomes : a success case and a failure case; i.e. required operation was performed or there was some error during operation.
Okay let’s start with callbacks
Callbacks
In essence, a callback function is a function that is passed as an argument to another function. The idea is that we have an extra argument at the end where we pass our function with the expectation that this function will be executed once the asynchronous operation is completed.
Let’s see this in action. Here I have a simple doSomething function for example (Not practical as my function is not even asynchronous). Although we generally don’t create our own asynchronous functions, it should be noted that we can if we want to. We generally deal with calling the asynchronous function (second part). If we see the doSomething function call, we can see an anonymous function being passed as an argument(no other arguments in this case) with error and result.
const doSomething = function (callback) {
// Success case
callback(undefined, "Success result here");
//...
// Error case
// callback('Error has occured!', undefined); // remember that callback can only be called once
}
doSomething((error, result) => {
if (error) {
return console.log(error);
}
console.log(result);
});
Let’s see a practical example. Here I want to find a customer with the given name from a mongoDB database.The findOne function is asynchronous. So, the first argument is the search query for the function and final argument on the other hand uses the callback pattern of sending an anonymous function with the success result and error.
db.collection('Customers').findOne({
name : 'Sherlock Holmes'
}, (error, data) => {
if (error) {
return console.log(error);
}
console.log(data);
})
The problem with the callback pattern is that it looks clunky and not as elegant as just doing something like (as in synchronous programming) :
const requiredUser = db.collection('Customers').findOne({name : 'Sherlock Holmes'});
Moreover imagine stacking multiple callbacks inside callbacks(People call it callback hell).
Promise
Okay, lets move on to another approach. Now with Promises, we don’t send a function to be executed on completion of the asynchronous operation. Rather, the idea is that the asynchronous function returns an object(called a Promise) when we call the function. So? What do I do with the object then? Remember that it all comes down to fact that we either expect the asynchronous operation to succeed(called resolved) or fail(called rejected). We use then() and catch() for these.
const doSomething = new Promise((resolve, reject) => {
// Success case
resolve('Result of successful operation');
//....
reject('Some error')
})
doSomething.then(result => {
console.log(result);
}).catch(error => {
console.log(error);
})
Here we use then() for result and at the end we use catch() to catch any errors.
Before seeing a real example, I want to show something called promise chaining; i.e. how we can stack promises a lot better than we could do for callbacks :
const add = function (a, b) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a + b);
}, 1000);
});
}
add(1,1).then(sum => {
return add(sum,3)
}).then(sum2 => {
console.log(sum2);
})
Here, what I wanted to show is how we can chain a bunch of promises together to essentially use the result of one promise to call another function. What I mean by that is I want the second sum() function call to wait for the result of the first one and only then execute.
Okay lets see the database example with promise.
db.collection('Customers').findOne({
name: 'Sherlock Holmes'
}).then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
This is indeed more easier to read that callbacks. But still, we haven’t achieved our dream of calling asynchronous functions as we do with synchronous ones like :
const sum1 = add(1,1);
const sum2 = add(sum1, 3);
console.log(sum2);
Async-await
Now comes the cool part. Before diving into async and await, we need to get something clear first. Its not like async-await is the next progressive thing from promises like going from callbacks to promises. It just helps/makes it easier to work with promises and helps us manage our asynchronous code.
What’s cool about async-await is that we can call asynchronous functions like we had wanted to above :
const add = (a,b) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(a + b);
},1000);
});
}
const doSmthg = async () => {
const sum1 = await add(3,4);
const sum2 = await add(sum1, 1);
console.log(sum2);
}
doSmthg();
See that we can just store the result of one add function call and the call the next one afterwards. As the name suggests, we ‘await’ the add function to complete its execution before the execution of the next await function. Okay, now let’s see how we use this. As of now, we have to use ‘await’ inside a function and this function has to be marked as asynchronous. We use ‘async’ infront of a function to indicate it as a asynchronous function where you intend to use ‘await’.
For the sake of completion, I will briefly note a few things :
-
Normal functions return whatever you return inside(or undefined if nothing is returned). But async functions always returns a promise which is fulfilled with the value you return :
- Normal function
const doSmthg = () => { return 'Hey'; } console.log(doSmthg()); --> Hey
- Async function
const doSmthg = async () => { return 'Hey'; } console.log(doSmthg()); --> Promise {Hey}
- Normal function
-
Remember that the only thing we are going to do differently is in handling the promise(ie. inside the doSmthg function where we call add). We don’t have to alter the asynchronous function add() (It would be different for callback and promise though). It still returns a promise.
-
To handle errors, use try and catch blocks
Thank you for making it to the end! I hope you learned something. I’ve probably left important bits of information or made few errors here and there as I am not an expert in Javascript. So, do let me know.