Mastering Asynchronous JavaScript: Promises, Async/Await, and Callbacks
Tags: javascript, webdev, tutorial, beginners
Published: 2024-07-07 07:15:15

Many operations in the real world, such as retrieving data from a server or reading a file, take time to finish. If these operations were synchronous, then we need to wait until the entire task is completed, resulting in a bad user experience. Asynchronous operations enable the program to continue executing other tasks while these operations are complete.

## 1. Understanding Callbacks

Callbacks are functions passed as arguments to other functions, which are then invoked when the task is complete.

``` js
// Example of a callback function
function fetchData(callback) {
    setTimeout(() => {
        callback("Data fetched from server");
    }, 2000);
}

// Using the callback
fetchData((message) => {
    console.log(message); // Outputs: Data fetched!
});
```

### Code Explanation

#### Function Definition:

The function `fetchData` is defined to accept one argument, `callback`.
`callback` is expected to be a function that will be called once the asynchronous operation inside `fetchData` is complete.

#### setTimeout:
Inside `fetchData`, there is a call to `setTimeout`.
`setTimeout` is a built-in JavaScript function that executes a function after a specified delay in milliseconds.
it is used to simulate a delay of 2000 milliseconds (2 seconds) to mimic an asynchronous operation like fetching data from a server.

#### Callback Invocation:
After the 2-second delay, the anonymous function passed to `setTimeout` is executed.
This function calls `callback` with the argument `"Data fetched from server"`.
This means after the delay, the `callback` function will be executed with the provided message.

#### Calling fetchData:
`fetchData` is called with an anonymous function as its argument. This anonymous function takes one parameter, `message`.
The anonymous function is the callback function that will be executed once the data is "fetched" (after the 2-second delay).

#### Callback Execution:
After 2 seconds, `setTimeout` triggers the execution of the callback with the message `"Data fetched from server"`.
The callback function logs the message to the console.

## 2. Working with Promises
A promise is an object representing the eventual completion or failure of an asynchronous call. It provides methods to handle asynchronous results and handle errors. It provide a consistent way to handle errors using .catch().

``` js
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("Data fetched from server");
        }, 2000);
    });
}

fetchData()
    .then((message) => {
        console.log(message); // Outputs: Data fetched!
    })
    .catch((error) => {
        console.error(error);
    });

```
### Code Explanation

### Function Definition:
The `fetchData` function is defined to return a new promise.
A promise is an object that represents an asynchronous operation that will eventually complete or fail.

### Creating the Promise:
Inside `fetchData`, a new promise is created using `new Promise((resolve, reject) => {...})`.
The promise constructor takes a function with two parameters, `resolve` and `reject`. These are functions that you call when the asynchronous operation completes successfully (`resolve`) or fails (`reject`).

### Simulating an Asynchronous Operation:
The `setTimeout` function is used to simulate a delay of 2000 milliseconds (2 seconds), representing an asynchronous operation like fetching data from a server.
After the 2-second delay, the resolve function is called with the message `"Data fetched from server"`.
This means the promise is successfully fulfilled after 2 seconds with the specified message.

### Calling fetchData:
The `fetchData` function is called, which returns a promise.

### Handling the Promise:
The `.then()` method is chained to the promise returned by `fetchData`.
The function passed to `.then()` is called when the promise is resolved successfully. It receives the message `"Data fetched from server"` and logs it to the console.

### Error Handling:
The `.catch()` method is chained to handle any errors if the promise is rejected.
If there were an error (for example, if `reject` were called inside the promise), the function passed to `.catch()` would be called to log the error.

## 3. Async/Await: Modern Asynchronous JavaScript
Async and Await are powerful keywords used for handling asynchronous operations using promises. Async functions returns a promise and await pauses the execution until Promise is resolved. It simplify asynchronous code and improve readability by making it appear synchronous.

``` js
async function fetchData() {
    try {
        let message = await new Promise((resolve) => {
            setTimeout(() => {
                resolve("Data fetched from server");
            }, 2000);
        });
        console.log(message); // Outputs: Data fetched!
    } catch (error) {
        console.error(error);
    }
}

fetchData();

```
### Code Explanation

### Async Function:
The `fetchData` function is declared with the `async` keyword. This means it can use `await` inside it and it returns a promise.

### Try-Catch Block:
The `try` block contains code that might throw an error. If an error occurs, the control moves to the `catch` block to handle the error.
The `catch` block catches and handles any errors that occur inside the try block.

### Awaiting a Promise:
Inside the `try` block, we have an `await` expression. It waits for the promise to resolve before moving to the next line of code.
The promise is created using `new Promise((resolve) => {...})`.
The `setTimeout` function is used to simulate a delay of 2000 milliseconds (2 seconds), representing an asynchronous operation like fetching data from a server.
After 2 seconds, the promise is resolved with the message `"Data fetched from server"`, and this message is assigned to the `message` variable.

### Logging the Message:
After the promise is resolved, the message is logged to the console using `console.log(message)`.

### Error Handling:
If an error occurs while waiting for the promise or during any operation in the `try` block, the control moves to the `catch` block.
The error is logged to the console using `console.error(error)`.

### Calling the Async Function:
The `fetchData` function is called. Since it's an `async` function, it runs asynchronously.

## Comparison Summary
Callbacks are simple and direct but can lead to messy code and inconsistent error handling.

Promises offer a more structured way to handle asynchronous operations and provide better error handling.

Async/Await builds on promises to provide even cleaner, more readable, and maintainable asynchronous code.