JavaScript-primer

Promise in JavaScript

In JavaScript, a promise is an object that represents the eventual completion or failure of an asynchronous operation. Promises provide a way to handle asynchronous operations in a structured and composable way, and are a key concept in modern JavaScript programming. A promise can be in one of three states:

- Pending: This is the initial state of a promise, and indicates that the asynchronous operation has not yet completed or failed.

- Fulfilled: This state indicates that the asynchronous operation has completed successfully, and the promise has a resolved value.

- Rejected: This state indicates that the asynchronous operation has failed, and the promise has a rejected value.

The state of a promise is determined by the resolve and reject functions passed to the Promise constructor. When the resolve function is called, the promise is set to the fulfilled state and the resolved value is stored in the promise. When the reject function is called, the promise is set to the rejected state and the rejected value is stored in the promise.

Once a promise has entered the fulfilled or rejected state, it cannot change its state. This is known as the “immutable” nature of promises.

A promise is created using the Promise constructor, which takes a function as an argument. The function is passed two arguments: resolve and reject, which are functions that are used to signal the completion or failure of the asynchronous operation.

Here is an example of using a promise:

const myPromise = new Promise((resolve, reject) => {
  // Perform an asynchronous operation, such as a network request
  setTimeout(() => {
    // If the operation is successful, call the resolve function
    resolve('Success');
  }, 1000);
});

// Use the then() method to handle the promise's completion or failure
myPromise.then((result) => {
  console.log(result); // Logs 'Success'
}, (error) => {
  console.error(error); // Not called
});

In this example, a promise is created using the Promise constructor. The function passed to the constructor performs an asynchronous operation (a timeout in this case) and calls the resolve function if the operation is successful. The then() method is used to handle the promise’s completion or failure, and the result and error arguments passed to the then() function are the value returned by the resolve and reject functions, respectively.


Understanding Promise Primary Functions

A promise object has three primary functions that can be used to handle the asynchronous operation that it represents:

- then(): This function is used to handle the completion or failure of the promise. It takes two arguments: a success callback function and a failure callback function. The success callback function is called when the promise is fulfilled, and the failure callback function is called when the promise is rejected.

- catch(): This function is used to handle the failure of the promise. It takes a single argument: a failure callback function that is called when the promise is rejected.

- finally(): This function is called when the promise is settled (either fulfilled or rejected), regardless of the outcome of the asynchronous operation. It does not take any arguments.

Here is an example of using the then(), catch(), and finally() functions with a promise:

const myPromise = new Promise((resolve, reject) => {
  // Perform an asynchronous operation, such as a network request
  setTimeout(() => {
    // If the operation is successful, call the resolve function
    resolve('Success');
  }, 1000);
});

// Use the then() function to handle the promise's completion or failure
myPromise.then((result) => {
  console.log(result); // Logs 'Success'
}, (error) => {
  console.error(error); // Not called
});

// Use the catch() function to handle the promise's failure
myPromise.catch((error) => {
  console.error(error); // Not called
});

// Use the finally() function to handle the promise's settlement
myPromise.finally(() => {
  console.log('Promise settled'); // Logs 'Promise settled'
});

In this example, a promise is created using the Promise constructor and is eventually fulfilled with the value ‘Success’. The then() function is used to handle the promise’s fulfillment, and the catch() and finally() functions are used to handle the promise’s failure and settlement, respectively.


Promise.race()

Promise.race() method returns a promise that resolves or rejects as soon as one of the promises in an iterable resolves or rejects, with the value or reason from that promise.

Here is an example of using Promise.race():

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(value => {
  console.log(value);  // Output: "two"
});


In this example, we have two promises that resolve with different values after different time intervals. We use Promise.race() to return a promise that resolves with the value of the first promise to resolve. In this case, promise2 resolves first, so the output is “two”.


Promise.all()

Promise.all() method returns a promise that resolves when all of the promises in an iterable have resolved, or rejects with the reason of the first passed promise that rejects.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values);  // Output: [3, 42, "foo"]
});

In this example, we have three promises: one that has already resolved, one that is a non-promise value, and one that will resolve after a delay. We use Promise.all() to return a promise that resolves with an array of the resolved values of the promises in the iterable. In this case, all of the promises have resolved, so the output is an array containing the resolved values of the promises.

Both Promise.race() and Promise.all() can be useful for handling multiple asynchronous operations and reacting to the resolution or rejection of those operations in a specific way.


Async / Await (Alternative to Then/Catch)

In JavaScript, the async/await syntax is a way to write asynchronous code in a synchronous-like style. It allows developers to write asynchronous code that reads like synchronous code, making it easier to understand and reason about.

The async keyword is used to define an asynchronous function, and the await keyword is used to pause the execution of the function until a promise is fulfilled or rejected.

Here is an example of using async/await to handle a promise:

async function getData() {
  // Perform an asynchronous operation, such as a network request
  const response = await fetch('https://example.com/data');

  // If the operation is successful, return the response
  return response.json();
}

// Use the getData() function
getData().then((data) => {
  console.log(data); // Logs the data returned by the promise
});

In this example, the getData() function is defined as an asynchronous function using the async keyword. The function uses the await keyword to pause its execution until the fetch() function returns a response. Once the response is returned, the function continues executing and returns the parsed JSON data.

The getData() function returns a promise, which can be handled using the then() function as shown. The then() function is called with the data returned by the promise as an argument.

async/await is a powerful and convenient way to write asynchronous code in JavaScript, and is often used in combination with other async/await features and technologies such as the fetch API and JavaScript libraries or frameworks.


Understanding CallBack Hell

“Callback hell” is a term used to describe a situation where you have multiple nested callback functions in your code, which can make it difficult to read and understand. It can occur when you are using asynchronous code, such as when making HTTP requests, and need to wait for a response before taking further action.

Here is an example of code that could be considered “callback hell”:

fetch('http://example.com/api/data1', (error, response) => {
  if (error) {
    console.error(error);
  } else {
    fetch('http://example.com/api/data2', (error, response) => {
      if (error) {
        console.error(error);
      } else {
        fetch('http://example.com/api/data3', (error, response) => {
          if (error) {
            console.error(error);
          } else {
            // Do something with the data
          }
        });
      }
    });
  }
});

In this example, the code is making three sequential HTTP requests, and using callback functions to handle the responses. However, the code is difficult to read because of the many nested callback functions. It can be hard to see the overall structure and flow of the code, and it can be difficult to add new functionality or debug any issues that may arise.

To avoid “callback hell,” you can use techniques such as Promises, async/await, or async functions to write code that is easier to read and understand. These techniques allow you to write asynchronous code that looks and behaves more like synchronous code, which can make it easier to write, maintain, and debug your applications.