# Mastering JavaScript: Async JS & Browser APIs

Asynchronous programming in JavaScript allows you to perform long-running operations without blocking the main thread, ensuring your applications remain responsive. This guide covers the key concepts and techniques for handling asynchronous operations, including `setTimeout`, `setInterval`, callbacks, promises, and `async/await`. We'll also dive into working with APIs using `XMLHttpRequest` and the `Fetch` API.

## Asynchronous JavaScript

### setTimeout

The `setTimeout` function executes a function after a specified delay.

**Syntax:**

```javascript
setTimeout(function, delay, ...args)
```

* `function`: The function to execute.
    
* `delay`: Time in milliseconds before the function is executed.
    
* `args`: Additional arguments to pass to the function.
    

**Example:**

```javascript
setTimeout(() => {
  console.log('Hello after 2 seconds');
}, 2000);
```

**Example with arguments:**

```javascript
setTimeout((message) => {
  console.log(message);
}, 2000, 'Hello after 2 seconds');
```

### setInterval

The `setInterval` function repeatedly executes a function at specified intervals.

**Syntax:**

```javascript
setInterval(function, interval, ...args)
```

* `function`: The function to execute.
    
* `interval`: Time in milliseconds between each execution.
    
* `args`: Additional arguments to pass to the function.
    

**Example:**

```javascript
setInterval(() => {
  console.log('Hello every 2 seconds');
}, 2000);
```

**Example with arguments:**

```javascript
setInterval((message) => {
  console.log(message);
}, 2000, 'Hello every 2 seconds');
```

### Clearing Timers

To stop a `setTimeout` or `setInterval`, use `clearTimeout` or `clearInterval`.

**Example:**

```javascript
const timeoutId = setTimeout(() => {
  console.log('This will not run');
}, 2000);

clearTimeout(timeoutId);

const intervalId = setInterval(() => {
  console.log('This will not run');
}, 2000);

clearInterval(intervalId);
```

**Example with named function:**

```javascript
function greet() {
  console.log('This will not run');
}

const timeoutId = setTimeout(greet, 2000);
clearTimeout(timeoutId);

const intervalId = setInterval(greet, 2000);
clearInterval(intervalId);
```

### Callbacks

Callbacks are functions passed as arguments to other functions and executed after some operation completes.

**Example:**

```javascript
function fetchData(callback) {
  setTimeout(() => {
    callback('Data fetched');
  }, 2000);
}

fetchData(data => {
  console.log(data); // "Data fetched" after 2 seconds
});
```

#### Example with error handling

```javascript
function fetchData(callback) {
  setTimeout(() => {
    if (Math.random() < 0.5) {
      callback(null, 'Data fetched');
    } else {
      callback('Error fetching data', null);
    }
  }, 2000);
}

fetchData((error, data) => {
  if (error) {
    console.error(error);
  } else {
    console.log(data); // "Data fetched" after 2 seconds
  }
});
```

#### Callback Hell

Callback hell occurs when callbacks are nested within other callbacks, making the code hard to read and maintain.

**Example:**

```javascript
fetchData(data1 => {
  console.log(data1);
  fetchData(data2 => {
    console.log(data2);
    fetchData(data3 => {
      console.log(data3);
      // And so on...
    });
  });
});
```

![Sheridan IMM on X: "Compare to the Callback Hell, promise and async/await  would save you much more time while working with asynchronous code. Follow  us on Sheridan IMM for more interesting coding](https://pbs.twimg.com/media/FMsFUfIWQAYn9iw.jpg align="center")

### Promises

Promises provide a cleaner way to handle asynchronous operations, avoiding callback hell.

**Syntax:**

```javascript
const promise = new Promise((resolve, reject) => {
  // Asynchronous operation
});
```

**Example:**

```javascript
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data fetched');
    }, 2000);
  });
};

fetchData()
  .then(data => {
    console.log(data); // "Data fetched" after 2 seconds
  })
  .catch(error => {
    console.error(error);
  });
```

#### Example with both resolve and reject

```javascript
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() < 0.5) {
        resolve('Data fetched');
      } else {
        reject('Error fetching data');
      }
    }, 2000);
  });
};

fetchData()
  .then(data => {
    console.log(data); // "Data fetched" after 2 seconds
  })
  .catch(error => {
    console.error(error); // "Error fetching data"
  });
```

#### Chaining Promises

You can chain promises to handle multiple asynchronous operations in sequence.

**Example:**

```javascript
const fetchData1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data 1 fetched');
    }, 1000);
  });
};

const fetchData2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data 2 fetched');
    }, 1000);
  });
};

fetchData1()
  .then(data1 => {
    console.log(data1);
    return fetchData2();
  })
  .then(data2 => {
    console.log(data2);
  })
  .catch(error => {
    console.error(error);
  });
```

#### async/await

`async/await` provides a syntax for writing asynchronous code in a synchronous style, making it easier to read and maintain.

**Example:**

```javascript
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data fetched');
    }, 2000);
  });
};

const getData = async () => {
  try {
    const data = await fetchData();
    console.log(data); // "Data fetched" after 2 seconds
  } catch (error) {
    console.error(error);
  }
};

getData();
```

#### Example with both resolve and reject

```javascript
const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() < 0.5) {
        resolve('Data fetched');
      } else {
        reject('Error fetching data');
      }
    }, 2000);
  });
};

const getData = async () => {
  try {
    const data = await fetchData();
    console.log(data); // "Data fetched" after 2 seconds
  } catch (error) {
    console.error(error); // "Error fetching data"
  }
};

getData();
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1722097445804/d1fc001a-9d37-43b7-aa46-08d594d81a13.png align="center")

### Handling Multiple Promises

Using `Promise.all`, you can handle multiple promises in parallel.

**Example:**

```javascript
const fetchData1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data 1 fetched');
    }, 1000);
  });
};

const fetchData2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data 2 fetched');
    }, 1000);
  });
};

const getData = async () => {
  try {
    const results = await Promise.all([fetchData1(), fetchData2()]);
    console.log(results); // ["Data 1 fetched", "Data 2 fetched"]
  } catch (error) {
    console.error(error);
  }
};

getData();
```

#### Using `Promise.race`

`Promise.race` returns the first promise that settles (either resolves or rejects).

**Example:**

```javascript
const fetchData1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data 1 fetched');
    }, 1000);
  });
};

const fetchData2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data 2 fetched');
    }, 500);
  });
};

Promise.race([fetchData1(), fetchData2()])
  .then(data => {
    console.log(data); // "Data 2 fetched" after 500 ms
  })
  .catch(error => {
    console.error(error);
  });
```

#### Using `Promise.allSettled`

`Promise.allSettled` returns a promise that resolves after all of the given promises have either resolved or rejected.

**Example:**

```javascript
const fetchData1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Data 1 fetched');
    }, 1000);
  });
};

const fetchData2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('Error fetching data 2');
    }, 500);
  });
};

Promise.allSettled([fetchData1(), fetchData2()])
  .then(results => {
    results.forEach((result) => {
      if (result.status === 'fulfilled') {
        console.log(result.value);
      } else {
        console.error(result.reason);
      }
    });
  });
```

## Working with APIs

APIs (Application Programming Interfaces) allow you to interact with external services or data sources. There are several ways to make HTTP requests in JavaScript.

### XMLHttpRequest

`XMLHttpRequest` is an older way to make HTTP requests.

It provides a way to interact with servers using a browser's built-in `XMLHttpRequest` object.

**Example:**

```javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
  if (xhr.status === 200) {
    console.log(xhr.responseText);
  } else {
    console.error('Request failed');
  }
};
xhr.send();
```

**Example with error handling:**

```javascript
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onload = function() {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log(xhr.responseText);
  } else {
    console.error('Request failed with status', xhr.status);
  }
};
xhr.onerror = function() {
  console.error('Network error');
};
xhr.send();
```

### Fetch

The `Fetch` API provides a modern way to make HTTP requests. It returns promises, making it easier to handle asynchronous operations.

**Example:**

```javascript
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('There has been a problem with your fetch operation:', error);
  });
```

**Example with different HTTP methods:**

```javascript
// GET request
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('There has been a problem with your fetch operation:', error);
  });

// POST request
fetch('https://api.example.com/data', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ key: 'value' }),
})
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('There has been a problem with your fetch operation:', error);
  });
```

#### Using async/await with Fetch

**Example:**

```javascript
const fetchData = async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('There has been a problem with your fetch operation:', error);
  }
};

fetchData();
```

![10 JavaScript Memes To Lighten Up Your Day - JavaScript in Plain English](https://miro.medium.com/v2/resize:fit:1000/0*4-b8TyA4Ahqa_ch5 align="center")

### Handling Errors

When making HTTP requests, it's important to handle errors gracefully.

**Example with Fetch:**

```javascript
const fetchData = async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('There has been a problem with your fetch operation:', error);
  }
};

fetchData();
```

### Sending Data

You can send data using the `Fetch` API by specifying the request method and providing the data in the request body.

**Example:**

```javascript
const sendData = async (data) => {
  try {
    const response = await fetch('https://api.example.com/data', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const result = await response.json();
    console.log(result);
  } catch (error) {
    console.error('There has been a problem with your fetch operation:', error);
  }
};

sendData({ key: 'value' });
```

## Conclusion

Asynchronous JavaScript is essential for building responsive and efficient applications. This guide has covered various techniques, including `setTimeout`, `setInterval`, callbacks, promises, and `async/await`. Additionally, we explored working with APIs using `XMLHttpRequest` and the `Fetch` API. Understanding these concepts and how to handle asynchronous operations effectively will help you write better JavaScript code and build more dynamic web applications. By mastering these topics, you'll be well-equipped to handle the complexities of modern JavaScript development.

In the next article, we'll dive deeper into some advanced JavaScript concepts, such as Iterators and Generators, Classes, and Modules. These topics will further enhance your understanding of JavaScript and enable you to write cleaner, more efficient code. Stay tuned for an in-depth exploration of these powerful features!

%%[bmc-singhlify]
