JS: Promise Tutorial

By Xah Lee. Date: . Last updated: .

(new in ECMAScript 2015)

What is a promise

Purpose of promise

Promise is for async code dealing with operations that might take a long time.

Basic promise syntax

this is the code to setup a promise:

const xpromise = new Promise(f_executor); xpromise.then(f_on_fulfilled, f_on_rejected);

the f_executor, f_on_fulfilled, f_on_rejected are functions you have to write.

The promise state

A Promise object has a state, and always in one of the following three conditions:

Once a promise state changed to fulfilled or rejected, it stays in that state, does not change.

the promise state is internal. there is no public variable for it. but the state is observed via then and other methods.

Example. most basic promise setup.

const f_executor = (f_resolve, f_reject) => {
 // here is our fake operation that takes long time.
 setTimeout(() => {
  const is_success = Math.random() > 0.5;

  if (is_success) {
   f_resolve("Winning number is 8306");
  } else {
   f_reject("Error number is 7431.");
  }
 }, 1000);
};

const f_on_fulfilled = (x) => {
 console.log("Success. Result is:", x);
};

const f_on_rejected = (x) => {
 console.log("Failed. message is:", x);
};

const xpromise = new Promise(f_executor);

xpromise.then(f_on_fulfilled, f_on_rejected);

/*
result.
prints
Success. Result is: Winning number is 8306
or
Failed. message is: Error number is 7431.
*/

Outline of using promise

/* Create a f_executor function.
The f_executor function is what you want to do, an operation that might take long time. */
const f_executor = (f_resolve, f_reject) => {
 /* do some operation here that might take long time. */

 /* Call f_resolve(x_result) if the operation is successful. The x_result is a value such as database query result. The x_result is optional. */

 /* Call f_reject(x_error) if the operation failed. x_error is any value or error object you want to indicate failure message. x_error is optional. */

/* If f_resolve is called, promise is set to fulfilled.
If f_reject is called, promise is set to rejected. */

 /* The names of the parameters f_resolve, f_reject doesn't matter.
Can be any name.
They are internal functions.
They do nothing.
JavaScript monitors which one is called to set promise state. */
};

// s------------------------------
// define functions on what to do when successful or failure

const f_on_fulfilled = (xresult) => {
 console.log(`f_on_fulfilled called with arg ${xresult}`);
};

const f_on_rejected = (xerror) => {
 console.log(`f_on_rejected called with arg ${xerror}`);
};

// s------------------------------
// create a promise and setup up function handlers for the promise state.

const xpromise = new Promise(f_executor);

xpromise.then(f_on_fulfilled, f_on_rejected);

/* if promise is fulfilled, JavaScript calls f_on_fulfilled(x_result).
x_result came from the arg we passed to f_resolve in f_executor. */

/* if promise is rejected, JavaScript calls f_on_rejected(x_error).
x_error came from the arg we passed to f_reject in f_executor. */

Math gist of promise

const xpm = new Promise(ff);
xpm.then(f_aa, f_bb);

here's the math essence.

in the code

const xpm = new Promise(ff);

ff must be a function. And ff must have 2 parameters aa, bb.

in ff, it may call aa(xval), xval is any value.

in ff, it may call bb(xerr), xerr is any value.

if aa is called first, we say the state is fulfilled. if bb is called first, we say the state is rejected. if none are called, we say the state is pending.

what are aa bb don't matter. They are internal functions created by JavaScript. They do-nothing.

return values of ff, aa, bb, are all ignored.

When new Promise(ff) is execeuted, JavaScript execute ff(fxx,fyy), where fxx fyy are functions of one parameter, created by JavaScript and they do nothing.

in the code

xpm.then(f_aa, f_bb);

the f_aa and f_bb must be functions. each has 1 parameter.

if the promise xpm is fulfilled, f_aa is called with xval passed to it as arg, like this: f_aa(xval).

if the promise xpm is rejected, f_bb is called with xerr passed to it as arg, like this: f_bb(xerr).

special case of f_resolve

in f_resolve(xval), if xval is a promise, the status of the promise is the status of xval.

const f_executor = (f_resolve, f_reject) => {
 const xval = new Promise((a, b) => {});
 f_resolve(xval);
};

const xpm = new Promise(f_executor);

xpm.then(
 (x) => {
  console.log(`Fullfilled. result is ${x}`);
 },
 (x) => {
  console.log(`Rejected. result is ${x}`);
 },
);

/*
nothing is printed.
because the promise xpm's state is pending.
because xval in f_resolve(xval) is a promise, and that promise is pending. */

if xval is the promise f_resolve(xval) is in, the status of the promise is rejected.

// example where resolve(xpromise) call the promise itself is in
const xpromise = new Promise((resolve) => {
 // the promise doesn't exist yet at the moment the executor runs,
 // so we have to defer the call
 setTimeout(() => resolve(xpromise), 0);
});

xpromise.then(
 (x) => {
  console.log(`Fullfilled. result is ${x}`);
 },
 (x) => {
  console.log(`Rejected. result is ${x}`);
 },
);

// Rejected. result is TypeError: Chaining cycle detected for promise #<Promise>

special case of f_reject

in f_reject(x), the x has no effect on the state of promise. The status is still rejected.

// example where rejected(x) where x is a promise.
// the x has no effect. the original promise is still rejected.

const f_executor = (f_resolve, f_reject) => {
 const newprom = new Promise((res) => {
  res("great");
 });
 f_reject(newprom);
};

const xpm = new Promise(f_executor);

xpm.then(
 () => {
  console.log("yes");
 },
 () => {
  console.log("no");
 },
);

// result is, print
// no

if xval is the promise f_resolve(xval) is in, the status of the promise is rejected.

// example where resolve(p) call the promise itself is in
const p = new Promise((resolve) => {
  // the promise doesn't exist yet at the moment the executor runs,
  // so we have to defer the call
  setTimeout(() => resolve(p), 0);
});

p.catch(err => console.error(err.message));
// Chaining cycle detected for promise #<Promise>

the then method

syntax:

promise_object.then(f1,f2)

f1 and f2 are both functions of 1 parameter.

f1 and f2 are optional.

the then-method sets up handler functions to call on promise_object state. f1 or f2 is called as soon as the promise is settled.

The then-method returns a new promise object. (so it can be chained)

Throwing inside f1 or f2 in promise_object.then(f2,f2) rejects the promise created by the then-method.

the “catch” method

syntax:

promise_object.catch(ferr)

ferr is a function that takes one arg.

when the promise_object is rejected, ferr is called with an arg xerror, where xerror is the arg passed to reject function in the promise f_executor function body.

What's the difference between p.then(aa, bb) vs p.then(aa).catch(cc)

bb (the second argument of .then) only sees a rejection of p itself.

If aa throws, or returns a rejected promise, bb does not run. That rejection goes to whatever comes after this .then.

.catch sits after aa, so it sees:

What is thenable object?

an object with a then method.

a promise object is thenable.

This-binding in promise

this-binding in promise has value of undefined.