Promises

An asynchronous operation allows the computer to move on to other tasks while waiting for the operation to be completed. Promises are objects that represent the eventual outcome of an asynchronous operation. These can be in one of three states:

  • Pending: The initial state when the operation hasnΒ΄t completed yet

  • Fulfilled: The operation has completed successfully, and the promise stores a resolved value

  • Rejected: The operation has failed, and the promise stores a reason for the failure

A promise is called settled when it isn't pending, no matter if it was rejected or fulfilled.

  • To create a new promise, we instantiate an object of the Promise class. To the promise, we pass a function with two parameters, usually called resolve and reject. These will store a resolve and reject function from the Promise class, which will settle based on the results of asynchronous operations. This function is called an executioner function

const func = (resolve, reject) => { // The executioner function
 if (true) {          // Ab example with a simple condition
     resolve('I resolved!');
 } else {
     reject('I rejected!'); 
 }
}

const myPromise = new Promise(func); // Create a promise with the function
console.log(myPromise);

  • The setTimeout() function lets us execute a function after some time. The other instructions will be executed normally, while the function we pass to setTimeout() will be executed after the specified time

console.log("This is executed first");

function greetings(){    // Fuction to be executed after
  console.log('This is executed after 2 seconds');
}

setTimeout(greetings,2000); // We pass the function and the value in milliseconds
console.log("This is executed second, even if the instruction is after");

  • Promise objects have a method then that allows us to set instructions to be done after a promise settles. It takes two callback functions as arguments, which are referred to as the onFulfilled handler that should contain the logic when resolved, and the onRejected handler that should contain the logic when rejected. The then function can be invoked with one, both, or neither handler, and its handlers will receive the values returned by the resolve() and reject() functions of the promise

const myProm = new Promise((resolve, reject) => {
  let num = Math.ceil(Math.random()*10);
  if (num <= 5 ){      // Condition checked by the promise
    resolve(`Yay ${num} is small`);
  } else {
    reject(`Oh no ${num} is too big`);
  }
});

const onFulfilled = (resolvedValue) => { // Resolve handler
  console.log(resolvedValue);
};

const onRejected = (rejectionReason) => { // Reject handler
  console.log(rejectionReason);
};

myProm.then(onFulfilled, onRejected); // Then uses the handlers and the promise

  • Promise objects also have a method catch which function is to manage the onRejected handler, instead of calling both handlers with then, allowing the separation of responsibilities, which is a better practice

// We transformed the instantiation to a function that returns a promise
// This allows the promise to receive parameters
const myPromise = (myNum) => {
  return new Promise((resolve, reject) => {
    let num = Math.ceil(Math.random()*10);
    if (num <= myNum ){
      resolve(`Yay ${num} is small`);
    } else {
      reject(`Oh no ${num} is too big`);
    }
  });
};

myPromise(5)     // We synthesize the syntax to be more readable     
  .then((resolvedValue) => {      // Then handles the rejected state
    console.log(resolvedValue);
  })
  .catch((rejectionReason) => {   // Catch handles the rejected state
    console.log(rejectionReason);
  }) 

  • When we have multiple operations that depend on each other to execute or that must be executed in a certain order, we can chain multiple promises. If any of the promises fail, it will settle to rejected and close all the promises

firstPromise()     // The first promise is used  
  .then((resolvedValue) => {
    return secondPromise(resolvedValue); // Returns the second promise with the resolved value of the first promise
  })
  .then((resolvedValue) => {   // Define the second promise
    console.log(resolvedValue); // Use resolved value of the second promise
  })
  .catch((rejectionReason) => { // This happens if any if the promises is rejected
    console.log(rejectionReason);
  });

  • When we have multiple operations that donΒ΄t depend on each other or have an order, we can use the .all() function to use concurrency and set multiple asynchronous operations. It takes an array of promises and returns a single promise. If any of the promises are rejected, the overall promise is rejected, and if all the promises are resolved, the overall promise too, and all resolved values are returned in an array

let myPromises = Promise.all([promOne(), promTwo(), promThree()]);

myPromises
  .then((arrayOfResponses) => { // If resolved, returns and array with all values
    console.log(arrayOfValues);
  })
  .catch((rejectionReason) => { // If any rejected, this is used
    console.log(rejectionReason);
  });

Async-Await

JavaScript uses an event loop to handle asynchronous function calls. When a program is run, function calls are made and added to a stack. The requests that need to wait for servers to respond are then sent to a separate queue. Once the stack has cleared, the functions in the queue are executed. The event loop is used to create a smoother browsing experience by handling asynchronous functions.

  • The async keyword is used to declare a function that handles a sort of asynchronous actions, which return a promise, and if a non-promise value is returned, it will return a promise resolved to that value, or resolved to undefined if anything is returned

async function myAsyncFunc() {
  return 5;
};

const myFunc = async () => { // Also setting them with arrow functions 
  return 5
};

myAsyncFunc()  // Promise returned
  .then(resolvedValue => {
    console.log(resolvedValue);
  })

  • The await keyword can only be used within an async function, and it suspends the execution of the function while waiting for a promise to resolve. It assigns the settled value of the promise instead of returning it completely

let myPromise = () => {  // Promise to be used
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Resolved')
    }, 1000);
  });
}

async function noAwait() {
 let value = myPromise(); // Execution is not paused
 console.log(value);
}

async function yesAwait() { // Execution is paused until completed the promise
 let value = await myPromise();
 console.log(value);
}

noAwait(); // Prints Promise { <pending> } as promise wasn't completed
yesAwait(); // Prints Resolved

  • We can also use multiple await statements to simulate the behavior of dependent promises

async function asyncAwaitMultiple(){  // This execute the promises in order
  const firstValue = await returnsFirstPromise();
  const secondValue = await returnsSecondPromise(firstValue);
  const thirdValue = await cookTheBeans(secondValue);
  return thirdValue;
}

asyncAwaitMultiple();

  • We can also use await only in the result of the promises to make concurrence in the execution. Instead of waiting for the result of one to execute the other, the promises are started at the same time and executed in parallel, and we only wait for the result

async function waiting() {  // Sequential execution
  const firstValue = await firstAsyncThing();
  const secondValue = await secondAsyncThing();
  console.log(firstValue, secondValue);
}

async function concurrent() { // Concurrent execution
  const firstPromise = firstAsyncThing();
  const secondPromise = secondAsyncThing();
  console.log(await firstPromise, await secondPromise);
}

  • A try-catch statement lets us handle whether the promises have been executed successfully or not. The try block will be run if all the promises are settled, and the catch statement will run if there was an exception (error)

async function tryCatch() {
 try {
   let resolveValue = await asyncFunction('thing that will fail');
   let secondValue = await secondAsyncFunction(resolveValue);
 } catch (err) {
   // Catches any errors in the try block
   console.log(err);
 }
}

tryCatch();

  • We can also execute promises concurrently using await with the Promise.all() function. This lets to stop the execution as soon as one of the promises is rejected

async function asyncAll() {
  const result = await Promise.all([asyncTask1(), asyncTask2(), asyncTask3()]);
  for (let i = 0; i < result.length; i++){
    console.log(resultArray[i]); // Iterate over resolved values of each promise
  }
}

Last updated