Jon Rumsey

An online markdown blog and knowledge repository.


Project maintained by nojronatron Hosted on GitHub Pages — Theme by mattgraham

Promises

Many languages utilize Promises to manage asynchronous operations.

Review the purpose, use, and gotchas of Promises.

Develop a list of key takeaways that the future me can review.

Overview

A Promise is:

A Promise has one of 3 states:

Note: There is a 'settled' state which is short-hand for fulfilled or rejected, and not pending.

Asynchronous Operations and The JS Event Loop

Event Loop is an execution stack that manages functions, popping them off after execution.

The Event Loop has a Message Queue that pushes functions onto the stack when it is empty.

Callbacks (callback functions) are used as asynchronous technique:

Callbacks can be used to encourage a set of functions that includes an asynchronous function (like calling a web API) to execute in-order.

Promises are inserted into the Microtask (Job) Queue in the JS Event Loop:

Construction

// initialize a promise
const promise = new Promise((resolve, reject) => {});

// init with a resolve and reject function
const betterPromise = new Promise((resolve, reject) => {
  resolve('good value');
  reject('an error occurred');
});

Only construct a new Promise instance to wrap functions that do not already support promises.

Generally speaking, consuming promises will be more common than creating them.

Consuming Promises

From the previous example, the 'betterPromise' object has functions that execute depending on the result.

Handle the promise using the following design:

// the PromiseValue when resolved will be 'good value'
// and because the resolve method is synchronous it
// will immediately be resolved to variable 'response'
betterPromise.then((response) => {
  console.log(response);
});

When calling an API that will take time to return, use a function that will return a promise.

Handling the result is fairly simple:

addWordsToDB(listOfWords)
  .then((results) => console.log('added list of words result:', results))
  .catch((err) => console.log('an error occurred:', err.message));

Use .then() to pass-in the resolved function of the promise (fulfilled) :arrow_right: Resolve handler.

Use .catch() to handle an error condition in the reject function of the promise :arrow_right: Reject handler.

Lazy-evaluation vs Promises

In JavaScript, lazy methods are different than Promises.

In other languages, lazy methods might be referred to as Promises, and might behave differently than JS Promise will.

// a lazy method is an arrow function
const lazy = () => expression;

// call the lazy method when ready
lazy();

// js hoisting plays a part

Promise Instance Methods

Promise methods associate further action taken when a Promise is settled, and can be chained.

addWordsToDB(listOfWords)
  .then((addResult) => processResult(addResult))) // a simplified callback
  .then((processedResult) => console.log('added and processed list of words result:', processedResult))
  .catch((err) => console.log('an error occurred:', err.message))

Look mom, no pyramids!

Input arguments are callbacks, which can be written as arrow functions (value)=>{return}.

Each handler takes one execution tick, so limiting handlers will speed up Promise method execution.

Promise methods are executed in FIFO order but encapsulated such that the result of the first method is passed to the next method.

In the case of a thrown error, the error reason is the result that is passed to the next method.

Promise.prototype.then()

Promise.prototype.catch()

Promise.prototype.finally()

Settled Promise Behavior

An action can be assigned to a settled promise. That action would be executed at the next asynchronous cycle (tick).

JS Promises were built to be compatible with previous Promise-like libraries.

Using Promise.resolve(thenable) is actually using a 'thenable' in place of a promise.

Promise.reject('reason') creates a new Promise object that is rejected with the given reason.

Concurrent Promise Methods

Use these methods to handle various concurrent Promise fulfill/reject behavior from an iterable of promises (or thenables):

Promises vs Async Await

The Syntactic Sugar of async and await make working with promises even easier.

Instead of using then() and catch() use the Try-Catch code pattern, executing 'fulfilled' code in the Try and handling 'rejected' in the catch block.

Note: There is a new 'top-level await' that can be used at the top of a module, eliminating the need for a module function to be labeled with 'await'. This is new enough that not all browsers currently support it, but this should make async-await code patterns a bit simpler. MDN Await Operator

Takeaways

Functions that will return a Promise should either be awaited or handled by chaining Resolve and Catch.

Functions are the basis upon which all JavaScript execution is directed.

Need to know how to use a promise example in another context :arrow_right: Use a callback i.e. call a function to do it for you.

Think of Promises as a way to leverage the asynchronous handling pipeline of the JavaScript Message Queue in the Event Loop.

Refresh your understanding the JS Event Loop et al

MDNs Basic Example

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code.
  // In reality, you will probably be using something like XHR or an HTML API.
  setTimeout(() => {
    resolve('Success!'); // Yay! Everything went well!
  }, 250);
});

myFirstPromise.then((successMessage) => {
  // successMessage is whatever we passed in the resolve(...) function above.
  // It doesn't have to be a string, but if it is only a succeed message, it probably will be.
  console.log(`Yay! ${successMessage}`);
});

References

Return to ContEd Index

Return to Root README