About Javascript Asynchronous Callbacks and promises & async
Published: 2022-06-29 23:28:17

I am going to introduce about Javascript technologies.

Asynchronous programming in JavaScript is one of the fundamental concepts to grasp to write better JavaScript.

Today, we'll learn about asynchronous JavaScript, with some real-world examples and some practical examples as well. Along with this article, you'll understand the functioning of:

Asynchronous Callbacks
Promises
Async / Await
**Table of content**
1 - Synchronous Programming vs Asynchronous Programming

2 - Asynchronous Callbacks: I'll call back once I'm done!

3 - Promises in JavaScript: I promise a result!

4 - Async/Await: I'll execute later!

1 - Synchronous vs Asynchronous
Before going into asynchronous programming, let's talk about synchronous programming first.

For example,
let greetings = "Hello World.";

let sum = 1 + 10;

console.log(greetings);

console.log("Greetings came first.")

console.log(sum);
You'll have an output in this order.
Hello World.
Greetings came first.
11
That's synchronous. Notice that while each operation happens, nothing else can happen.

JavaScript is monothread at its core: when one block of code is being executed, no other block of code will be executed.

Asynchronous programming is different. To make it simple, when JavaScript identifies asynchronous tasks, it'll simply continue the execution of the code, while waiting for these asynchronous tasks to be completed.

Asynchronous programming is often related to parallelization, the art of performing independent tasks in parallel.

How is it even possible?

Trust me, we do things in an asynchronous way without even realizing it.

Let's take a real-life example to better understand.

Real life example: Coffee shop

Now that you have a clear idea about how asynchronous programming works, let's see how we can write asynchronous with :

Asynchronous callbacks
Promises
And the async/await syntax.
2 - Asynchronous Callbacks: I'll call back once I'm done!
A callback is a function passed as an argument when calling a function (high-order function) that will start executing a task in the background.
And when this background task is done running, it calls the callback function to let you know about the changes.
function callBackTech(callback, tech) {
    console.log("Calling callBackTech!");
    if (callback) {
        callback(tech);
    }
    console.log("Calling callBackTech finished!");
}

function logTechDetails(tech) {
    if (tech) {
        console.log("The technology used is: " + tech);
    }
}

callBackTech(logTechDetails, "HTML5");
Output

Output Callback

As you can see here, the code is executed each line after each line: this is an example of synchronously executing a callback function.

And if you code regularly in JavaScript, you may have been using callbacks without even realizing it. For example :

array.map(callback)
array.forEach(callback)
array.filter(callback)
let fruits = ['orange', 'lemon', 'banana']

fruits.forEach(function logFruit(fruit){
    console.log(fruit);
});
Output
orange
lemon
banana
But callbacks can also be executed asynchronously, which simply means that the callback is executed at a later time than the higher-order function.

Let's rewrite our example using setTimeout() function to register a callback to be called asynchronously.
function callBackTech(callback, tech) {
    console.log("Calling callBackTech!");
    if (callback) {
        setTimeout(() => callback(tech), 2000)
    }
    console.log("Calling callBackTech finished!");
}

function logTechDetails(tech) {
    if (tech) {
        console.log("The technology used is: " + tech);
    }
}

callBackTech(logTechDetails, "HTML5");
Output

Output Async callbacks

In this asynchronous version, notice that the output of logTechDetails() is printed in the last position.

This is because the asynchronous execution of this callback delayed its execution from 2 seconds to the point where the currently executing task is done.

Callbacks are old-fashioned ways of writing asynchronous JavaScript because as soon you have to handle multiple asynchronous operations, the callbacks nest into each other ending in callback hell.

Callback Hell

To avoid this pattern happening, we will see now Promises.

3 - Promise: I promise a result!
Promises are used to handle asynchronous operations in JavaScript and they simply represent the fulfillment or the failure of an asynchronous operation.

Thus, Promises have four states :

pending: the initial state of the promise
fulfilled: the operation is a success
rejected: the operation is a failure
settled: the operation is either fulfilled or settled, but not pending anymore.
This is the general syntax to create a Promise in JavaScript.
let promise = new Promise(function(resolve, reject) {
    ... code
});
resolve and reject are functions executed respectively when the operation is a success and when the operation is a failure.

To better understand how Promises work, let's take an example.

Jack's Mom: Hey Jack! Can you go to the store and get some milk? I need more to finish the cake.
Jack: For sure, Mom!
Jack's Mom: While you are doing that, I'll be dressing the tools to make the cake. (Async task) In the meantime, let me know if find it. (success callback)
Jack: Great! But what if I don't find the milk?
Jack's Mom: Then, get some chocolate instead. (Failure callback)
This analogy isn't terribly accurate, but let's go with it.

Here's what the promise will look like, supposing that Jack has found some milk.
let milkPromise = new Promise(function (resolve, reject) {

    let milkIsFound = true;

    if (milkIsFound) {
        resolve("Milk is found");
    } else {
        reject("Milk is not found");
    }
});
Then, this promise can be used like this:
milkPromise.then(result => {
    console.log(result);
}).catch(error => {
    console.log(error);
}).finally(() => {
    console.log("Promised settled.");
});
Here :

then(): takes a callback for success case and executes when the promise is resolved.
catch(): takes a callback, for failure and executes if the promise is rejected.
finally(): takes a callback and always returns when the premise is settled. It's pretty useful when you want to perform some cleanups.
Let's use a real-world example now, by creating a promise to fetch some data.
let retrieveData = url => {

    return new Promise( function(resolve, reject) {

        let request = new XMLHttpRequest();
        request.open('GET', url);

        request.onload = function() {
          if (request.status === 200) {
            resolve(request.response);
          } else {
            reject("An error occured!");
          }
        };
        request.send();
    })
};
The XMLHttpRequest object can be used to make HTTP request in JavaScript.

Let's use the retrieveData to make a request from https://swapi.dev, the Star Wars API.
const apiURL = "https://swapi.dev/api/people/1";

retrieveData(apiURL)
.then( res => console.log(res))
.catch( err => console.log(err))
.finally(() => console.log("Done."))
Here's what's the output will look like.

Output

Output

Rules for writing promises
You can't call both resolve or reject in your code. As soon as one of the two functions gets called, the promise stops and a result is returned.
If you don’t call any of the two functions, the promise will hang.
You can only pass one parameter to resolve or reject. If you have more things to pass, wrap everything in an object.
4 - async/await: I'll execute when I am ready!
The async/await syntax has been introduced with ES2017, to help write better asynchronous code with promises.

Then, what's wrong with promises?
The fact that you can chain then() as many as you want makes Promises a little bit verbose.
For the example of Jack buying some milk, he can :

call his Mom;
then buy more milk;
then buy chocolates;
and the list goes on.
milkPromise.then(result => {
        console.log(result);
    }).then(result => {
        console.log("Calling his Mom")
    }).then(result => {
        console.log("Buying some chocolate")
    }).then(() => {
        ...
    })
    .catch(error => {
        console.log(error);
    }).finally(() => {
        console.log("Promised settled.");
    });
Let's see how we can use async/await to write better asynchronous code in JavaScript.

The friend party example
Jack is invited by his friends to a party.

Friends: When are you ready? We'll pick you.
Jack: In 20 minutes. I promise.
Well, actually Jack will be ready in 30 minutes. And by the way, his friends can't go to the party without him, so they'll have to wait.

In a synchronous way, things will look like this.
let ready = () => {

    return new Promise(resolve => {

        setTimeout(() => resolve("I am ready."), 3000);
    })
}
The setTimeout() method takes a function as an argument (a callback) and calls it after a specified number of milliseconds.

Let's use this Promise in a regular function and see the output.

function pickJack() {

    const jackStatus = ready();

    console.log(`Jack has been picked: ${jackStatus}`);

    return jackStatus;
}

pickJack(); // => Jack has been picked: [object Promise]
Why this result? The Promise hasn't been well handled by the function pickJack.
It considers jackStatus like a regular object.

It's time now to tell our function how to handle this using the async and await keywords.

First of all, place async keyword in front of the function pickJack().
async function pickJack() {
    ...
}
By using the async keyword used before a function, JavaScript understands that this function will return a Promise.
Even, if we don't explicitly return a Promise, JavaScript will automatically wrap the returned object in a Promise.

And next step, add the await keyword in the body of the function.
    ...
    const jackStatus = await ready();
    ...
await makes JavaScript wait until the Promise is settled and returns a result.

Here's how the function will finally look.
async function pickJack() {

    const jackStatus = await ready();

    console.log(`Jack has been picked: ${jackStatus}`);

    return jackStatus;
}

pickJack(); // => "Jack has been picked: I am ready."
And that's it for async/await.

This syntax has simple rules:

If the function you are creating handles async tasks, mark this function using the async keyword.

await keyword pauses the function execution until the promise is settled (fulfilled or rejected).

An asynchronous function always returns a Promise.

Here's a practical example using async/await and the fetch() method. fetch() allows you to make network requests similar to XMLHttpRequest but the big difference here is that the Fetch API uses Promises.

This will help us make the data fetching from https://swapi.dev cleaner and simple.
async function retrieveData(url) {
    const response = await fetch(url);

    if (!response.ok) {
        throw new Error('Error while fetching resources.');
    }

    const data = await response.json()

    return data;
};
const response = await fetch(url); will pause the function execution until the request is completed.
Now why await response.json()? You may be asking yourself.

After an initial fetch() call, only the headers have been read. And as the body data is to be read from an incoming stream first before being parsed as JSON.

And since reading from a TCP stream (making a request) is asynchronous, the .json() operations end up asynchronous.

Then let's execute the code in the browser.
retrieveData(apiURL)
.then( res => console.log(res))
.catch( err => console.log(err))
.finally(() => console.log("Done."));
And that's all for async/await

Conclusion
In this article, we learned about callbacks, async/await and Promise in JavaScript to write asynchronous code. If you want to learn more about these concepts, check these amazing resources.

An Interesting Explanation of async/await in JavaScript
Everything About Callback Functions in JavaScript
Promises basics And as every article can be made better so your suggestion or questions are welcome in the comment section