Asynchronous JavaScript

Creating Promises

Back to course index

A Promise is an object that represents the current state of an operation (an asynchronous operation, by design). A Promise object is a placeholder for a value that isn’t known at the time the Promise is created, but into which a result will, uh, result — a object that represents an asynchronous operation, the terms by which the operation is considered a success or failure, the actions to be taken in either case, and the value that results.

A Promise instance is created by using the new operator to invoke the built-in Promise constructor function, which accepts a function called the executor as an argument:

Code language
js

function theExecutor() {};
const thePromise = new Promise( theExecutor );

console.log( thePromise );
// Result: Promise { <state>: "pending" }


That executor function is typically used to perform one or more asynchronous actions, then dictate the terms by which the Promise should be considered successfully fulfilled or rejected. When a new Promise is created, the executor is called and the code inside it is executed, exactly the way you would expect:

Code language
js

function theExecutor() {
  console.log( "Started" );
};

const thePromise = new Promise( theExecutor );
// Result: Started

console.log( thePromise );
// Result: Promise { <state>: "pending" }

The resulting object is very much unlike any other we’ve encountered so far, though. This Promise shall forever remain pending, as this executor doesn’t do much of anything — asynchronous or otherwise — and we haven’t told it to result in anything.

Seeing “we haven’t told it to result in anything” regarding a function might make it easy to mistake a Promise for a fancy way of using a callback that returns a value, but that’s not the case:

Code language
js

function theExecutor() {
  return true;
};

const thePromise = new Promise( theExecutor );

console.log( thePromise );
// Result: Promise { <state>: "pending" }

Still pending, and that returned value isn’t reflected anywhere in the resulting object. A Promise represents the result of the executor function, whether that result is provided by us, or occurs during the course of the executor’s execution. For example, when an executor function results in an error — either one we threw or one that bubbled up from some function we called — the state of the resulting Promise is rejected:

Code language
js

function theFunction() {
  throw new Error( "Whoops." );
};

const thePromise = new Promise( theFunction );
// result: Uncaught Error: Whoops.

console.log( thePromise );
// Result: Promise { <state>: "rejected", <reason>: Error }

The executor function is called with two arguments: a function that reports that the work done by the executor has been completed successfully, and a function that reports failure.

A Promise is resolved if the executor function is completed without error and the “resolve” argument is called. That function accepts a single argument that provides the value of the Promise:

Code language
js

function theFunction( resolveFunc, rejectFunct ) {
  resolveFunc( "Nothing happened, but successfully!" );
};

const thePromise = new Promise( theFunction );

console.log( thePromise );
// Result (Firefox): Promise { <state>: "fulfilled", <value>: "Nothing happened, but successfully!" }
// Result (Chrome): Promise {<fulfilled>: 'Nothing happened, but successfully!'}

A Promise is rejected if the “reject” argument is called or the executor function encounters an error, the latter of which you’ve already seen. The argument passed to the reject function is used as the rejection value of the Promise. The reject function has semantics like throw, and results in an error:

Code language
js

// Error
function theFunction( resolveFunc, rejectFunc ) {
  rejectFunc( "Nothing happened, and that is bad." );
};

const thePromise = new Promise( theFunction );

console.log( thePromise );
// Result (Firefox): Promise { <state>: "rejected", <reason>: "Nothing happened, and that is bad." }
// Result (Chrome): Promise {<rejected>: 'Nothing happened, and that is bad.'}
// Uncaught (in promise) Nothing happened, and that is bad.

After a Promise is fulfilled or rejected, it’s considered settled — and once settled, the value and state of that Promise object will never change.

Now, before we go any further, I gotta come clean, here: illustrative though they may have been, these have all been lousy Promises. None of them do anything asynchronous — these aren’t doing anything that a regular old function couldn’t do, but they’re doing it with way more cognitive overhead. Fundamentally, a Promise is intended to represent the result of an asynchronous operation — what you’ve seen so far here are basically just clunky callback functions.

So now that you’ve seen how a Promise works mechanically, let’s write a slightly better one — one that involves an actual asynchronous operation:

Code language
js

const theExecutor = ( resolve, reject ) => {
  // Resolve after five seconds:
  setTimeout(() => {
    resolve( "Time's up." );
  }, 5000);
};

const thePromise = new Promise( theExecutor );

console.log( thePromise );
// result: Promise { <state>: "pending" }

That’s more like it. If you run the above snippet in your developer console all at once, you’ll get a pending Promise — the setTimeout is still running, and the Promise hasn’t resolved yet. If you wait five seconds and check the value of thePromise again:

Code language
js

const theExecutor = ( resolve, reject ) => {
  // Resolve after five seconds:
  setTimeout(() => {
    resolve( "Time's up." );
  }, 5000);
};

const thePromise = new Promise( theExecutor );

// Wait five seconds:

console.log( thePromise );
// result: Promise { <state>: "fulfilled", <value>: "Time's up." }

A settled, resolved Promise — the end result of an asynchronous operation. To try out a real rejection, we’ll use an asynchronous operation guaranteed to fail, via the Fetch API — a built-in interface for making HTTP requests and handling their responses — which returns a Promise.

If you paste the following in your developer console, well, a lot is going to happen, a little bit at a time:

Code language
js

const pingWilto = fetch( "<https://wil.to>", {});

console.log( pingWilto );
// Result: Promise { <state>: "pending" }

/*
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at <https://wil.to/>. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.

Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.
*/

// Wait a few seconds...

console.log( pingWilto );
// Result: Promise { <state>: "rejected", <reason>: TypeError }

Right from the jump, the call to console.log( pingWilto ) should result in a still-pending Promise, since the HTTP request won’t have come back yet. When the request does complete, you’ll get an error, because a browser won’t allow cross-origin requests like this one — the call never goes through, so to speak, and an error is thrown. If you check the value of pingWilto after the asynchronous operation has completed, you’ll get see that the Promise is rejected. Again the Promise represent the results of the asynchronous operation — in this case, that it failed.

Now that we have Promises that reflect the results of an asynchronous operation, let’s put them to use in the way that only Promises can be used: whenever.