Promise States and Syntax

saurabhmisra

Saurabh Misra

Posted on July 12, 2021

Promise States and Syntax

In the previous article in this series, we learnt that asynchronous operations are executed by the environment or outside JS. So using callbacks, JS and in turn our code has had no way to interact with these operations while they were in progress or even after they had finished executing. All we could do is wrap our success and failure code in callback handlers and pass them to these operations and leave it upto them to invoke these handlers as and when they complete.

What is a Promise?

A promise is a special kind of JS object which represents an asynchronous operation. It's like a placeholder object that you can use instead of the actual response from the asynchronous operation while it's in progress. The promise object will make the response available to you as soon as the asynchronous operation completes. You can even interact with the promise object after the asynchronous operation is complete which is something that wasn't possible before.

Since a promise object is just like a regular JS object available immediately in our code, we can write code and interact with it synchronously. Imagine that! With promises, we can interact with asynchronous operations...synchronously!

Promises basically give synchronous superpowers to asynchronous operations. 🦸‍♂️

Promise States

Since an asynchronous operation can be either in progress, successfull or failed, Promises can also be in 3 corresponding states:

  1. pending - means the asynchronous operation is in progress.
  2. fulfilled - means the asynchronous operation has completed successfully.
  3. rejected - means the asynchronous operation has failed.

You'll also hear the term settled with Promises. A Promise is said to be settled if it is either in the fulfilled or rejected state but not in the pending state. This is not actually a state of the promise but just a term used for convenience to mean that the promise is not pending.

Creating a Promise

We can create Promises by using the Promise() constructor. This constructor takes in a single argument which is a function called the executor function. The executor function, in turn, accepts two functions as inputs. The standard convention is to name these two functions as resolve() and reject(), however, you can name them whatever you like.

var executor = ( resolve, reject ) => {};
var promise = new Promise( executor );
console.log( promise );

// Promise { <state>: 'pending' }
Enter fullscreen mode Exit fullscreen mode

The above bare-bones Promise creation statement creates a promise object in the initial pending state.

Resolving a Promise

The executor function houses the code that initiates an asynchronous operation. If this operation completes successfully, we invoke resolve() which changes the promise state from pending to fulfilled.

var promise = new Promise( (resolve, reject) => {

    setTimeout(() => {
        resolve();
        console.log( promise );
    }, 1000);

});

// Promise { <state>: "fulfilled", <value>: undefined }
Enter fullscreen mode Exit fullscreen mode

In the above example, we initiate an asynchronous operation using a setTimeout() inside the executor function. When the timeout completes, we call resolve() to instruct the promise that the timeout has completed successfully. This will change the status of the Promise from pending to fulfilled so when the console.log() prints the promise, you can see that the state of the promise is now fulfilled.

A promise is always fulfilled with a value. Since we have not provided a value, it's fulfilled with undefined. If we provide a value as an input argument to resolve(), the promise will be fulfilled with that value.

var promise = new Promise( (resolve, reject) => {

    setTimeout(() => {
        resolve( "I am now fulfilled😇" );
        console.log( promise );
    }, 1000);

});

// Promise { <state>: "fulfilled", <value>: "I am now fulfilled😇" }
Enter fullscreen mode Exit fullscreen mode

Rejecting a Promise

If the asynchronous operation fails, we invoke reject() inside the executor function which changes the state from pending to rejected. Similar to resolve(), if you don't specify a reason for this error, it'll be set as undefined.

var promise = new Promise( (resolve, reject) => {

    setTimeout(() => {
        reject();
        console.log( promise );
    }, 1000);

});

// Promise { <state>: "rejected", <reason>: undefined }
// Uncaught (in promise) undefined
Enter fullscreen mode Exit fullscreen mode

If you do specify a reason as an input argument to reject(), then the promise will be rejected with that reason.

var promise = new Promise( (resolve, reject) => {

    setTimeout(() => {
        reject( "something went wrong...💩" );
        console.log( promise );
    }, 1000);

});

// Promise { <state>: "rejected", <reason>: "something went wrong...💩" }
// Uncaught (in promise) something went wrong...💩
Enter fullscreen mode Exit fullscreen mode

Note: You don't have to worry about defining resolve() and reject(). The Promise constructor takes care of that internally. You only need to call these functions inside the executor function when necessary. Think about it like this, the promise object knows what to do when an asynchrounous operation succeeds or fails, meaning it knows what to do when you call resolve() or reject(). But it doesn't know when that happens and how to interpret completion as either success or failure. So we tell it that by calling resolve() on success and reject() on failure.

State change is a one-way street

Once the promise goes from the pending state to either fulfilled or rejected, it stays there...like...for good. It cannot go back to pending. Neither can a fulfilled promise be rejected or vice versa. A fulfilled promise cannot be fulfilled again and a rejected promise cannot be rejected again. This ensures that our program will run asynchronous code for either the success scenario or the failure scenario, but never both. It also ensures that the program will execute either of them only once. These are guarantees that we don't have with callbacks since we pass them away as arguments and have no control over how they are invoked.


So we know how to create promises using the Promise() constructor and how promises transition from one state to another. But in actual web development, you'll find yourself consuming promises already created by Web APIs or third party libraries much more often than creating them using the Promise() constructor. The Promise() constructor is mostly used for promisifying or wrapping older APIs(like we have done with setTimeout() in the previous examples) so that they return promises.

The next section will introduce you to methods for promise consumption like then() and catch() and how we can chain promises together to give more structure to our asynchronous code. See you there!

💖 💪 🙅 🚩
saurabhmisra
Saurabh Misra

Posted on July 12, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related