# Callback Functions, Promises, Async/Await and Fetching API's

## Callback Functions

A callback function is a function that is passed as an argument to another function and is executed after some operation has been completed. Callbacks are commonly used for asynchronous operations in JavaScript, such as fetching data from an API, reading files, or handling events.

```javascript
function greet(name, callback) {
  console.log(`Hello, ${name}!`);
  callback();
}

function sayGoodbye() {
  console.log('Goodbye!');
}

greet("Aquib", sayGoodbye);
// Output:
// Hello, Aquib!
// Goodbye!
```

In this example, `sayGoodbye` is passed as a callback to the `greet` function. After `greet` logs the greeting, it calls `sayGoodbye`.

### Asynchronous Callbacks

Callbacks are especially useful in asynchronous operations, such as handling API requests or reading files, where you need to perform actions after an operation completes without blocking the execution of the code.

```javascript
function delayedGreeting(name, callback) {
  setTimeout(() => {
    console.log(`Hello, ${name}!`);
    callback();
  }, 2000); // 2 seconds delay
}

function notify() {
  console.log('Greeting completed!');
}

delayedGreeting("Aquib", notify);
// Output (after 2 seconds delay):
// Hello, Aquib!
// Greeting completed!
```

In this example, the greeting is delayed by 2 seconds using `setTimeout`, and after the greeting, the `notify` function is called.

### Using Anonymous Functions as Callbacks

Anonymous functions are often used as callbacks, especially for simple operations.

```javascript
function processArray(arr, callback) {
  for (let i = 0; i < arr.length; i++) {
    arr[i] = callback(arr[i]);
  }
  return arr;
}

const numbers = [1, 2, 3, 4];
const doubled = processArray(numbers, function(num) {
  return num * 2;
});

console.log(doubled); // Output: [2, 4, 6, 8]
```

In this example, an anonymous function is passed as a callback to `processArray` to double each number in the array.

### Callback Hell

Callback hell, also known as the "Pyramid of Doom," refers to a situation where callbacks are nested within other callbacks multiple levels deep, making the code hard to read and maintain.

```javascript
doSomething(function(result1) {
  doSomethingElse(result1, function(result2) {
    doAnotherThing(result2, function(result3) {
      doFinalThing(result3, function(result4) {
        console.log("Done!");
      });
    });
  });
});
```

In ES6 JavaScript introduced `Promise()`, this is a replacement of callbacks because as you saw in the last example "Callback Hell", now we can handle the callbacks with even better and easier way.

## Promises

Promises are a modern way to handle asynchronous operations in JavaScript. They provide a cleaner, more readable, and more manageable alternative to traditional callback-based approaches. A promise represents a value that may be available now, or in the future, or never.

### Promise Structure

1. **Pending**: The initial state, neither fulfilled nor rejected.
    
2. **Fulfilled**: The operation completed successfully.
    
3. **Rejected**: The operation failed.
    

A promise is created using the `Promise` constructor, which takes a function (executor) with two parameters: `resolve` and `reject`.

```javascript
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  if (success) {
    resolve("Operation was successful!");
  } else {
    reject("Operation failed!");
  }
});
```

### Using Promises

Promises are consumed using the `then`, `catch`, and `finally` methods.

#### `then` Method

The `then` method is used to handle a fulfilled promise.

```javascript
myPromise.then((message) => {
  console.log(message); // Output: Operation was successful!
});
```

#### `catch` Method

The `catch` method is used to handle a rejected promise.

```javascript
const myPromise = new Promise((resolve, reject) => {
  const success = false;

  if (success) {
    resolve("Operation was successful!");
  } else {
    reject("Operation failed!");
  }
});

myPromise.catch((error) => {
  console.error(error); // Output: Operation failed!
});
```

#### `finally` Method

The `finally` method is executed regardless of the promise's outcome, whether it is fulfilled or rejected.

```javascript
myPromise.finally(() => {
  console.log("Promise has been settled."); // Output: Promise has been settled.
});
```

### Chaining Promises

Promises can be chained to handle a sequence of asynchronous operations.

```javascript
const fetchData = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Data fetched"), 1000);
});

fetchData
  .then((data) => {
    console.log(data); // Output: Data fetched
    return "Processing data";
  })
  .then((processMessage) => {
    console.log(processMessage); // Output: Processing data
    return "Data processed";
  })
  .then((finalMessage) => {
    console.log(finalMessage); // Output: Data processed
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log("All operations completed"); // Output: All operations completed
  });
```

Let's understand this with a small demonstration, we will order a pizza:

```javascript
function orderPizza () {
    return new Promise(function(res, rej) {
        setTimeout(() => {
            const pizza = "pizza";
            res(pizza);
            rej("An error occured while ordering pizza.")
        }, 2000);
    });
}

const pizzaPromise = orderPizza();

pizzaPromise.then((pizza) =>{
    console.log(`my ${pizza} has been ordered.`)
}).catch(error => {
    console.log(error)
}) // Output: My pizza has been ordered.
```

what would happen if we were not able to place the order? let's see:

```javascript
function orderPizza () {
    return new Promise(function(res, rej) {
        setTimeout(() => {
            const pizza = "pizza";
            //res(pizza);
            rej("An error occured while ordering pizza.")
        }, 2000);
    });
}

const pizzaPromise = orderPizza();

pizzaPromise.then((pizza) =>{
    console.log(`my ${pizza} has been ordered.`)
}).catch(error => {
    console.log(error)
}) // Output: An error occured while ordering pizza.
```

* Here there was no response `res` so it went for reject `rej` and threw an error inside `.catch`.
    

## Async/Await

Async/await is a modern syntax in JavaScript that allows you to write asynchronous code in a more synchronous and readable manner. It is built on top of Promises and provides a way to handle asynchronous operations more gracefully without deeply nested callbacks or complex promise chains.

### The `async` Keyword

The `async` keyword is used to define an asynchronous function. An `async` function always returns a Promise. If the function returns a value, the Promise will be resolved with that value. If the function throws an error, the Promise will be rejected with that error.

#### Syntax

```javascript
async function functionName() {
  // function body
}
```

```javascript
async function greet() {
    return "Hello, World!";
}

greet().then((message) => console.log(message)); // Output: Hello, World!
```

### The `await` Keyword

The `await` keyword is used to pause the execution of an `async` function until a Promise is resolved or rejected. It can only be used inside `async` functions. `await` makes the code wait for the Promise to resolve and returns the resolved value. If the Promise is rejected, `await` throws the rejected value.

#### Syntax

```javascript
let result = await promise;
```

```javascript
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function sayHello() {
  console.log('Start');
  await delay(2000); // Waits for 2 seconds
  console.log('Hello!');
  console.log('End');
}

sayHello();
// Output:
// Start
// (2 seconds delay)
// Hello!
// End
```

### Combining `async` and `await`

Let's combine `async` and `await` to perform asynchronous operations in a more readable way. We will again be creating an order pizza function:

```javascript
function getCheese() {
    return new Promise((res, rej) => {
        setTimeout(() => {
            res("🧀");
        }, 1000);
    });
}

function getDough(cheese) {
    return new Promise((res, rej) => {
        setTimeout(() => {
            res(`${cheese} 🫓`);
        }, 2000);
    });
}

function getPizza(dough) {
    return new Promise((res, rej) => {
        setTimeout(() => {
            res(`${dough} 🍕`);
        }, 2000);
    });
}

async function orderPizza() {
    const cheese = await getCheese();
    const dough = await getDough(cheese);
    const pizza = await getPizza(dough);

    return `Here is your ${pizza}`;
}

orderPizza().then((pizza) => {
    console.log(pizza);
}) // Output: (after 5 seconds) Here is your 🧀 🫓 🍕
```

<mark>Async/await makes asynchronous code easier to read and maintain by allowing you to write it in a synchronous style.</mark>

## `fetch` API

The `Fetch` API is a modern, promise-based method for making HTTP requests in JavaScript. It provides a more powerful and flexible feature set than the older `XMLHttpRequest` and is much easier to use.

### Usage

The `fetch` function is used to make a request to a specified URL. It returns a promise that resolves to the response of the request.

**Syntax**

```javascript
fetch(url, options)
  .then(response => {
    // handle the response
  })
  .catch(error => {
    // handle errors
  });
```

We will only understand the GET request for now, Here's a real life example:

```javascript
async function fetchData() {
    try {                        // Random API from the internet.
        const response = await fetch("https://dummyjson.com/products/2");
        console.log(response);
                                // Converting to JSON format.
        const data = await response.json();
        console.log(data);
        
        //loadData function will be created on next JavaScript example.
        loadData(data);
        
    } catch (err) {
        console.log(err)
    }
}

fetchData(); // Output: A big object filled with many data's.
```

Now let's take an example that we want to use this data dynamically inside of our webpage:

```xml
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- Very basic code -->
    <h2 id="title">dummy text</h2>
    <img id="img" src="" alt="">

    <script src="fetch.js"></script>
</body>
</html>
```

In the last JavaScript example we called our function by `fetchData()`, after that line we will do a simple DOM manipulation:

```javascript
function loadData(data) {
    const title = document.getElementById("title");
    const img = document.getElementById("img")
    title.innerHTML = data.brand
    img.src = data.thumbnail
}
```

* Now the data will be dynamically added to the webpage without writing anything, you can add more fields by observing whats inside of the data object printed on your console.
    

<mark>On the creation of a dynamic website it is essential to understand how to fetch the data from the given API, not just GET request but POST request as well.</mark>
