Categories


Archives


Recent Posts


Categories


Promise State

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

Last time we said we’d be reaching the summit of this series and talking about async/await. However, after getting into async/await, I realized there was one more promise topic to cover. Today we’re going to have a quick primer on a promise’s state and lifecycle.

As with most of our code so far, all the examples assume you’re running NodeJS 10, but the concepts should apply to all modern versions of javascript.

Promise Lifecycle

A promise is, from one point of view, a container for holding a piece of asynchronous work — work that will either return a value, or produce an error. By having this work contained in a promise object, we can pass the work around in our program without having to wait for the asynchronous work to complete, and then ask the promise for its value when we need it.

As clients of the promise, we never really know what’s going on inside of it. We know that something’s going on, but we don’t get to know when the async work has started or finished. The only thing we’re allowed to ask the promise to do is call a function when the work has finished.

Internally though, the Promise is keeping track of its own status/state. There are official terms for these states — pending, fulfilled, and rejected. A pending promise is one where the async work hasn’t completed. A fulfilled promise is one where the async work has returned (i.e. resolved) a final value. A rejected promise is one where the async work has produced an error (i.e. rejected).

The next three sections will run through some simple programs that log a promise during its various states.

Pending

If we run the following small NodeJS program

// File: promise-state.js
const createPromise = function(message) {
    const promise = new Promise(function(resolve, reject){
        setTimeout(function(){
            if(message) {
                resolve("Hello World");
            } else {
                reject("No Message Provided")
            }
        }, 0);
    })
    return promise;
}

const promise = createPromise("Hello World")
console.log(promise);

we’ll see the following output.

$ node test.js
Promise { <pending> }

When we log out a promise, NodeJS lets us know that the promise is <pending>. Its work isn’t done yet.

Fulfilled

If we run the following similar small NodeJS program

// File: promise-state.js
const createPromise = function(message) {
    const promise = new Promise(function(resolve, reject){
        setTimeout(function(){
            if(message) {
                resolve("Hello World");
            } else {
                reject("No Message Provided")
            }
        }, 0);
    })
    return promise;
}

const promise = createPromise("Hello World")

promise.then(function(result) {
    console.log(promise);
});

we’ll see

$ node test.js
Promise { 'Hello World' }

Here NodeJS is telling us that the promise has fulfilled to a value, and that the value is a string, and that that string contains the text Hello World. A promise that had fulfilled to an object would look like this

$ node test.js
Promise { { someKey: 'Some Value' } }

Rejected

If we run this final small NodeJS program

// File: promise-state.js
const createPromise = function(message) {
    const promise = new Promise(function(resolve, reject){
        setTimeout(function(){
            if(message) {
                resolve("Hello World");
            } else {
                reject("No Message Provided")
            }
        }, 0);
    })
    return promise;
}

const promise = createPromise(false)

promise.then(null, function(result) {
    console.log(promise);
});

we’ll see

$ node test.js
Promise { <rejected> 'No Message Provided' }

Here NodeJS is telling us the promise is <rejected>, and lets us know what value the promise was rejected with.

Terminology Overload

Like a lot of things to do with async and javascript, we’re describing one view of a world that’s taken almost a decade to arrive. The terms “pending, fulfilled, and rejected” haven’t always been the one true way to refer to promise state. They’re probably not even the one true way today — but it is useful to have some concrete definitions in place before you go looking at the other branching paths promise terminology has taken.

One bit of confusion: When you reject a promise, its state becomes rejected. However, when you resolve a promise to a value, that promise’s state becomes fulfilled. In some promise libraries, there’s a difference between fulfilling and resolving a promise. Even with native promises, a promise might resolve to another promise (think of the multiple then callback chains we’ve already discussed), which means the original promise isn’t fulfilled yet.

Another term you’ll hear thrown around is settled. A settled promise is one that’s no longer pending — i.e. it’s either fulfilled or rejected. Confusingly, folks will also use the term resolved to refer to a promise that’s settled.

I say this is confusing because the system-developer/promise-creator also uses resolve to return the value of the asynchronous work, but the client developer may continue chaining additional promises onto the stack. Consider the following two ways of saying the same thing.

A promise chain may need to resolve several promises before the promise can be resolved.

A promise chain may need to resolve several promises before the promise can be settled.

The later seems like it offers a clearer description of what’s going on. This is also another example where, I think, the lack of a crisp line between the system and client developer creates a bit of confusion (the ability/requirement of client developers to, themselves, create additional promises when chaining work)

Extra Features

While we’re here, we’ll take another chance to throw some side eye at the eighty-plus promise libraries that implement the A+/Promise spec confusing things further. For example, earlier we said

As clients of the promise, we never really know what’s going on inside of it. We know that something’s going on, but we don’t get to know when the async work has started or finished. The only thing we’re allowed to ask the promise to do is call a function when the work has finished.

That’s true enough — except when it’s not. For example, if you’re using the popular Bluebird promise library you have access to something called the Synchronous inspection API, which includes the ability to check whether a promise is fulfilled, rejected, or still pending.

You also have access to a method to check whether a promise is canceled. Wait — canceled? What does that even mean? Well, in addition to the A+/Promise features, Bluebird promises will also let you cancel them. We’ll leave what cancellation means as an exercise for the reader — our main takeaway for today should be that promise terminology varies library to library, and you’ll want to be careful (or quiet) if you’re talking promises across projects with a different set of ideas about what a promise is.

Wrap Up

OK! With that primer out of the way, we’ll be all set to discuss the latest bit of thought technology to come out of the async world — async/await.

Series Navigation<< The Practical Problems of Javascript PromisesWhat is Async/Await Good For? >>

Copyright © Alan Storm 1975 – 2019 All Rights Reserved

Originally Posted: 29th July 2019