Asynchronous Promises in JavaScript.

Asynchronous Promises in JavaScript.

Asynchronous programming gives your program the leverage to start a continuous, long-running task and still run other shorter tasks/events while that task runs. With this method, your program would not need to wait for longer tasks to be completed before the shorter events are started, and they all can run simultaneously, presenting each result from respective events. Quite often, key functions in our programs are not run on short events, therefore they are asynchronous, since they may require a longer run time.

In asynchronous programming, promises are objects returned by an asynchronous function that represents the eventual completion or failure of an operation, this can be achieved by attaching handlers to the promise objects, which will eventually be executed when the operation reaches completion or is rejected. A promise has three states:-

  • Pending: The initial state of a promise. It means that the operation hasn't yet completed or failed.

  • Fulfilled: The operation reaches completion and returns a value.

  • Rejected: The operation failed, and it returns an error.

Using the fetch()API.

The fetch()API is a modern API for making resourceful requests in JavaScript, better than making use of older methods like XMLHttpRequest (XHR) API. So let's make use of the crime data Api to make a simple request to get some information, In an HTTP request, we send a request message to a remote server, and it sends us back a response. In this case, we'll send a request to get a JSON file from the server, using the server URL.

fetch('https://jsonplaceholder.typicode.com/users')
  .then(response => {
    if (!response.ok) {
      throw new Error('Server response not OK');
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Error fetching JSON:', error);
  });

So let me dissect this code for simplicity of understanding;

When the fetch() method is called, it initiates an HTTP request to the server to fetch the data from the specified URL. However, this process takes time, and the response from the server may not be available immediately. Instead of blocking the execution of the code or other units of functions until the response is received, the fetch() method returns a Promise immediately. This allows the rest of the code to continue executing while the network request is in progress.

In this code, Promises are used to handle the asynchronous nature of the network request and response. Inside the first then() block, we're checking if the response was successful using the ok property of the Response object. If the response is not ok, we're throwing an error using the throw statement.

 .then(response => {
    if (!response.ok) {
      throw new Error('Server response not ok');
    }
}

If the response is ok, we're calling the JSON() method of the Response object, which returns a Promise that resolves to the JSON data. We can then use another then() block to handle the JSON data.

  .then(response => {
    if (!response.ok) {
      throw new Error('Server response not OK');
    }
    return response.json();
  })
  .then(data => {
    console.log(data);
  })

Finally, we're using the catch() method to handle any errors that may occur during the fetch operation. If the server throws any error let's assume it's any of these;

  • 400 Bad Request: The request was malformed or invalid.

  • 401 Unauthorized: The request requires authentication, but the user has not provided valid credentials.

  • 403 Forbidden: The server understood the request, but is refusing to fulfill it.

  • 404 Not Found: The requested resource could not be found on the server.

  • 500 Internal Server Error: The server encountered an unexpected condition that prevented it from fulfilling the request.

    With the code below, the error will be caught by the catch block and handled accordingly

.catch(error => {
    console.error('Error fetching JSON:', error);
  });

Promises are used extensively in the Fetch API to handle the asynchronous nature of network requests and responses. They allow us to write asynchronous code that is more readable and easier to reason about.

When the data is fetched, we get an array of values containing the food name, price and food image. In displaying the data, you can decide to display only a single variable of the entire data, let's assume all you need to display is the name, you can modify your code by adding this to your line of code

.then(data => {
    const names = data.map(product => product.name);
    console.log(names);
  })

In the code above, The map() method is called on the data array to create a new array with the values returned by the callback function. In this case, the product parameter in the callback function represents each object in the data array, and we use dot notation to extract the name property from each object.

The resulting names array contains only the name of the properties of each object in the data array. This array is then logged to the console using console.log().

So, the purpose of this block of code is to extract the names of each product from the JSON data returned by the server and log them to the console for testing and debugging purposes. This is just one example of how we can manipulate and use the data retrieved from an HTTP request using Promises and the Fetch API.

In another tutorial, I will show you how to display the fetched information on your webpage, mapping in between individual object data.

Async & Await

Async/await is a syntactic feature of JavaScript that makes it easier to write asynchronous code in a synchronous style. The async keyword is used to define a function that returns a Promise, for example;

async function fetchBookCats(){
...
}

When the await keyword is introduced into this code, it is used to wait for the resolution of a Promise inside an async function. Also, await methods can only be used in an async function, e.g

async function fetchUsers() {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const usersData = await response.json();
  console.log(usersData[0].name);
}
fetchUsers();

In the code above, we have an asynchronous function called fetchBookCats, which makes a fetch request to the server and waits for a response to return. After which the data is extracted from the response and parsed as a JSON, then we attempt to get the name of the first book in the books category. Note that because fetch() returns a Promise, using await in this way allows us to write code that looks synchronous, even though it is asynchronous.

In the async method, it is possible to handle errors that may occur due to asynchronous operations using the try and catch block. So let's look at this for example

async function fetchUsers() {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    const usersData = await response.json();
    return usersData;
  }
  catch (error) {
    console.error(`Could not get products: ${error}`);
  }
}
const promise = fetchUsers();
promise.then((usersData) => console.log(usersData[0].name));

In summary, in the code above, the async function fetchUsers makes a request, and it uses the await keyword to wait for the promises to resolve. If the response is ok, response.json() is called to parse the response as JSON and return it as a Promise. The parsed JSON data is returned as the value of the Promise returned by fetchUsers(). Specifically in this code, the try block makes a network request using the fetch() function and checks whether the response is successful by checking the ok property. If the response is not successful, an error is thrown with the HTTP status code. If the response is successful, the response.json() method is called to parse the response as JSON.

If an error occurs within the try block, it is caught by the catch block. In the catch block, the error is logged to the console with an error message. This is important because it allows the developer to know what went wrong during the execution of the function and take appropriate action.

In conclusion, this article covered basically what asynchronous promises represent, how to utilize asynchronous operations in building modern and flexible javascript operations, the concept of useFetch, Async and Await, and how to apply its practice in building javascript applications for making fetch requests. Therefore, understanding and effectively utilizing asynchronous promises is crucial for modern and flexible JavaScript applications. By making use of concepts such as useFetch, Async, and Await, developers can harness the full potential of asynchronous operations, enabling them to create responsive, efficient, and user-friendly web experiences. Asynchronous programming not only enhances performance but also improves SEO rankings by ensuring faster loading times and smoother interactions.

Please do well to like and share, also if you need mentorship in javascript, you can reach out via any of my social handles. See you in my next article, bye.