JavaScript Asynchronous Ops - Promises

Content credits to Codecademy Intermediate JavaScript course, these are just my personal note taking for future references.

Summary

  • Promises are JavaScript objects that represent the eventual result of an asynchronous operation.

  • Promises can be in one of three states: pending, resolved, or rejected.

  • A promise is settled if it is either resolved or rejected.

  • We construct a promise by using the new keyword and passing an executor function to the Promise constructor method.

  • setTimeout() is a Node function which delays the execution of a callback function using the event-loop.

  • We use .then() with a success handler callback containing the logic for what should happen if a promise resolves.

  • We use .catch() with a failure handler callback containing the logic for what should happen if a promise rejects.

  • Promise composition enables us to write complex, asynchronous code that’s still readable. We do this by chaining multiple .then()‘s and .catch()‘s.

  • To use promise composition correctly, we have to remember to return promises constructed within a .then().

  • We should chain multiple promises rather than nesting them.

  • To take advantage of concurrency, we can use Promise.all().

Promises Basics

The Promise constructor method takes a function parameter called the executor function which runs automatically when the constructor is called. The executor function generally starts an asynchronous operation and dictates how the promise should be settled.

The executor function has two function parameters, usually referred to as the resolve() and reject() functions. The resolve() and reject() functions aren’t defined by the programmer. When the Promise constructor runs, JavaScript will pass its own resolve() and reject() functions into the executor function.

  • resolve is a function with one argument. Under the hood, if invoked, resolve() will change the promise’s status from pending to fulfilled, and the promise’s resolved value will be set to the argument passed into resolve().

  • reject is a function that takes a reason or error as an argument. Under the hood, if invoked, reject() will change the promise’s status from pending to rejected, and the promise’s rejection reason will be set to the argument passed into reject().

const executorFunction = (resolve, reject) => {
  if (someCondition) {
      resolve('I resolved!');
  } else {
      reject('I rejected!'); 
  }
}
const myFirstPromise = new Promise(executorFunction);
  • We declare a variable myFirstPromise

  • myFirstPromise is constructed using new Promise() which is the Promise constructor method.

  • executorFunction() is passed to the constructor and has two functions as parameters: resolve and reject.

  • If someCondition evaluates to true, we invoke resolve() with the string 'I resolved!'

  • If not, we invoke reject() with the string 'I rejected!'


const inventory = {
  sunglasses: 1900,
  pants: 1088,
  bags: 1344
};

// Write your code below:
const myExecutor = (resolve, reject) => {
  if (inventory.sunglasses > 0 ){
    resolve ('Sunglasses order processed.');
  } else {
    reject('That item is sold out.');
  }
}

const orderSunglasses = () => {
  return new Promise(myExecutor)
}
const orderPromise = orderSunglasses();
console.log(orderPromise);

Node setTimeout( )

  • node API that uses callback to schedule tasks after a delay

  • 2 parameters : callback function and delay in milliseconds

//BASIC USE
const delayedHello = () => {
  console.log('Hi! This is an asynchronous greeting!');
};
 
setTimeout(delayedHello, 2000); // at least 2 seconds delay, setTimeout NO CAPS FOR 'O'    
//Construct ASYNCHRONOUS PROMISES
const returnPromiseFunction = () => {
  return new Promise((resolve, reject) => {
    setTimeout(( ) => {resolve('I resolved!')}, 1000);
  });
};
 
const prom = returnPromiseFunction();

In the example code, we invoked returnPromiseFunction() which returned a promise. We assigned that promise to the variable prom. Similar to the asynchronous promises you may encounter in production, prom will initially have a status of pending.

Consuming Promises :

.then() method

  • tells computer what to do after rejected or resolved after promise

In the case of our dishwasher promise, the dishwasher will run then:

  • If our promise rejects, this means we have dirty dishes, and we’ll add soap and run the dishwasher again.

  • If our promise fulfills, this means we have clean dishes, and we’ll put the dishes away.

.then() has 2 callback functions as arguments:

When the promise settles, the appropriate handler will be invoked with that settled value.

  • The first handler, sometimes called onFulfilled, is a success handler, and it should contain the logic for the promise resolving.

  • The second handler, sometimes called onRejected, is a failure handler, and it should contain the logic for the promise rejecting.

Note that it always returns a promise.

// EXAMPLE USAGE
let prom = new Promise((resolve, reject) => {
  let num = Math.random();
  if (num < .5 ){
    resolve('Yay!');
  } else {
    reject('Ohhh noooo!');
  }
});
 
const handleSuccess = (resolvedValue) => {
  console.log(resolvedValue);
};
 
const handleFailure = (rejectionReason) => {
  console.log(rejectionReason);
};
 
prom.then(handleSuccess, handleFailure);

Explanation:

  • prom is a promise which will randomly either resolve with 'Yay!' or reject with 'Ohhh noooo!'.

  • We pass two handler functions to .then(). The first will be invoked with 'Yay!' if the promise resolves, and the second will be invoked with 'Ohhh noooo!' if the promise rejects.

// EXAMPLE PROMISE FOR STOCK CHECKING 
//1. Stock File - Library.js
const inventory = {
  sunglasses: 0,
  pants: 1088,
  bags: 1344
};

const checkInventory = (order) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let inStock = order.every(item => inventory[item[0]] >= item[1]);
            if (inStock) {
                resolve(`Thank you. Your order was successful.`);
            } else {
                reject(`We're sorry. Your order could not be completed because some items are sold out.`);
            }
        }, 1000);
    });
};
module.exports = {checkInventory};
//2. Handle Promises - app.js
const {checkInventory} = require('./library.js');

const order = [['sunglasses', 1], ['bags', 2]];

const handleSuccess = (resolvedValue) => {
  console.log(resolvedValue);
};

const handleFailure = (rejectReason) => {
  console.log(rejectReason);
};
checkInventory(order).then(handleSuccess,handleFailure);

.catch() method - quite a useless function, for readability

  • only takes one argument : onRejected()

  • activated on reason for rejection - failure handler

  • same as using .then() but only with a failure handler

prom
  .then((resolvedValue) => {
    console.log(resolvedValue);
  })
  .catch((rejectionReason) => {
    console.log(rejectionReason);
  });

Explanation:

  • prom is a promise which randomly either resolves with 'Yay!' or rejects with 'Ohhh noooo!'.

  • We pass a success handler to .then() and a failure handler to .catch().

  • If the promise resolves, .then()‘s success handler will be invoked with 'Yay!'.

  • If the promise rejects, .then() will return a promise with the same rejection reason as the original promise and .catch()‘s failure handler will be invoked with that rejection reason.

//BASICALLY
checkInventory(order)
.then(handleSuccess)
.catch(handleFailure);

Composition - Chaining Promises

firstPromiseFunction()
.then((firstResolveVal) => {
  return secondPromiseFunction(firstResolveVal);
})
.then((secondResolveVal) => {
  console.log(secondResolveVal);
});

Explanation :

  • We invoke a function firstPromiseFunction() which returns a promise.

  • We invoke .then() with an anonymous function as the success handler.

  • Inside the success handler we return a new promise— the result of invoking a second function, secondPromiseFunction() with the first promise’s resolved value.

  • We invoke a second .then() to handle the logic for the second promise settling.

  • Inside that .then(), we have a success handler which will log the second promise’s resolved value to the console.

In order for our chain to work properly, we had to return the promise secondPromiseFunction(firstResolveVal). This ensured that the return value of the first .then() was our second promise rather than the default return of a new promise with the same settled value as the initial.

// EXAMPLE
const {checkInventory, processPayment, shipOrder} = require('./library.js');

const order = {
  items: [['sunglasses', 1], ['bags', 2]],
  giftcardBalance: 79.82
};

checkInventory(order)
.then((resolvedValueArray) => {
  // Write the correct return statement here:
 return processPayment(resolvedValueArray)
})
.then((resolvedValueArray) => {
  // Write the correct return statement here:
  return shipOrder(resolvedValueArray)
})
.then((successMessage) => {
  console.log(successMessage);
})
.catch((errorMessage) => {
  console.log(errorMessage);
});
//TERMINAL INPUT
/* $ node app.js
 All of the items are in stock. The total cost of the order is 35.97.
 Payment processed with giftcard. Generating shipping label.
The order has been shipped. The tracking number is: 473611.
$ */

Promise.all() method - Concurrency to maximize efficiency

when you want all promises to be done concurrently to maximize efficiency

Promise.all() accepts an array of promises as its argument and returns a single promise. That single promise will settle in one of two ways:

  • If every promise in the argument array resolves, the single promise returned from Promise.all() will resolve with an array containing the resolve value from each promise in the argument array.

  • If any promise from the argument array rejects, the single promise returned from Promise.all() will immediately reject with the reason that promise rejected. This behavior is sometimes referred to as failing fast.

let myPromises = Promise.all([returnsPromOne(), returnsPromTwo(), returnsPromThree()]);
 
myPromises
  .then((arrayOfValues) => {
    console.log(arrayOfValues);
  })
  .catch((rejectionReason) => {
    console.log(rejectionReason);
  });

Explanation :

  • We declare myPromises assigned to invoking Promise.all().

  • We invoke Promise.all() with an array of three promises— the returned values from functions.

  • We invoke .then() with a success handler which will print the array of resolved values if each promise resolves successfully.

  • We invoke .catch() with a failure handler which will print the first rejection message if any promise rejects.

const onFulfill = (itemsArray) => {
  console.log(`Items checked: ${itemsArray}`);
  console.log(`Every item was available from the distributor. Placing order now.`);
};

const onReject = (rejectionReason) => {
	console.log(rejectionReason);
};

// Write your code below:
const checkSunglasses = checkAvailability('sunglasses', 'Favorite Supply Co.')
const checkPants = checkAvailability('pants', 'Favorite Supply Co.')
const checkBags = checkAvailability('bags', 'Favorite Supply Co.')
Promise.all([checkSunglasses, checkPants, checkBags])
.then(onFulfill)
.catch(onReject)
//TERMINAL INPUT
/* $ node app.js
Checking availability of sunglasses at Favorite Supply Co....
Checking availability of pants at Favorite Supply Co....
Checking availability of bags at Favorite Supply Co....
sunglasses are in stock at Favorite Supply Co.
bags are in stock at Favorite Supply Co.
Error: pants is unavailable from Favorite Supply Co. at this time. 
*/

Last updated