JavaScript Thenable Object


JavaScript

Introduction to JavaScript thenable

Promises are a big part of the JavaScript environment which allows us to represent the possible completion or failure of an asynchronous operation and the result for each possibility

JavaScript thenable is an object that holds or implements the then() method and can have two callbacks, one for when the Promise is fulfilled, and one for when the Promise is rejected. All Promises are thenable object, but not all thenable objects are promises.

All thenable object has a fundamental structure like the one below, and can have more properties than the then method but must have the then method to be called a thenable object.

const obj = {
    then() {
        
    }
}

The then method in the thenable object accesses the result of a settled or failed promise via the callbacks it accepts. JavaScript allows us to use thenables in place of promises via the use of the Promise resolve method, as it tracks thenable objects which all Promises have.

 

Promises in JavaScript

As stated earlier, Promises allow us to manage the completion or non-completion of an asynchronous operation.

With an example, let’s illustrate Promises and show you how it works. Say, we need to work with an API or URL, we can make use of a Promise to deal with a successful or failed call using the resolve and reject callbacks, then method to execute the resolve callback, and the catch to deal with the reject callback.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Promise</title>
    <script src="index.js" defer></script>

</head>
<body>
    
</body>
</html>

The JavaScript file, index.js, will contain the code below

function getData(url) {
    return new Promise((resolve, reject) => {
        if (!url) {
            reject("No URL provided");
        }

        const xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.send();
        xhr.onload = function () {
            if (xhr.status === 200) {
                resolve(xhr.responseText);
            } else {
                reject(xhr.status);
            }
        };
    });
}

const url = prompt("Enter a URL");

getData(url)
    .then((result) => {
        console.log("Success!");
        console.log(result);
    })
    .catch((status) => {
        console.log(`An error with status code ${status} occurred`);
    });

Output

The browser prompting the "Enter the URL" input

 

And after the OK, the below

"Success" logged into the console, followed by the API JSON response.

 

With this type of setup, we can have some guarantees and allows ourselves to chain multiple callbacks that will be invoked in the order they are inserted.

 

Using JavaScript thenables

Since you know how the then methods work, we can store the then method within an object making it a thenable rather than work it directly with a function as in the code in the above section. Remember that we said all Promise objects are thenable objects, we can work with the thenable as a Promise object and work our code more directly.

So, by rewriting the same code, we can make use of the Promise resolve method to call the then method inside the obj object (a thenable object).

const obj = {
    then(resolve, reject) {
        const url = "<https://reqres.in/api/users?page=2>";

        if (!url) {
            reject("No URL provided");
        }

        const xhr = new XMLHttpRequest();
        xhr.open("GET", url);
        xhr.send();
        xhr.onload = function () {
            if (xhr.status === 200) {
                resolve(xhr.responseText);
            } else {
                reject(xhr.status);
            }
        };
    },
};

Promise.resolve(obj).then((result) => {
    console.log(result);
});

Output

{"page":2,"per_page":6,"total":12,"total_pages":2,"data":[{"id":7,"email":"michael.lawson@reqres.in","first_name":"Michael","last_name":"Lawson","avatar":"<https://reqres.in/img/faces/7-image.jpg>"},...

 

Chaining Promises

The fact that you can return a promise (or any thenable) from a then/ catch/ finally handler to resolve the promise they create means that if you need to do a series of asynchronous operations that provide promises/thenables, you can use then on the first operation and have the handler return the thenable for the second operation, repeated as many times as you need. Here's a chain with three operations returning promises:

firstOperation()
.then(firstResult => secondOperation(firstResult)) // or: .then(secondOperation)
.then(secondResult => thirdOperation(secondResult * 2))
.then(thirdResult => { /* Use `thirdResult` */ })
.catch(error => { console.error(error); });

When that code runs, firstOperation starts the first operation and returns a promise. Calls to then and catch set up handlers for what happens next. Later, when the first operation completes, what happens next depends on what happened to the first operation: if it fulfills its promise, the first fulfillment handler gets run and starts the second operation, returning the promise secondOperation provides.

JavaScript Thenable Object

JavaScript Thenable Object

JavaScript Thenable Object

 

If the second operation fulfills its promise, that fulfills the promise the first then returned, and the next fulfillment handler gets run: it starts the third operation and returns the promise of its result. If the third operation fulfills its promise, that fulfills the promise from the second then. That calls the code that uses the third result. Provided that code doesn't throw or return a rejected promise, the chain completes. The rejection handler doesn't get run at all in that case, since there were no rejections.

JavaScript Thenable Object

 

If the first operation rejects its promise, though, that rejects the promise from the first then, which rejects the promise from the second then, which rejects the promise from the third then, which calls the rejection handler at the end. None of the then callbacks get called, because they were for fulfillment, not rejection.

JavaScript Thenable Object

 

If the first operation succeeds and its fulfillment handler starts the second operation and returns its promise, but the second operation fails and rejects its promise, that rejects the promise from the first then, which rejects the promise from the second then, which rejects the promise from the third then, which calls the rejection handler. Similarly, if the first operation succeeds but the fulfillment handler throws, the same thing happens: the remaining fulfillment handlers are skipped, and the rejection handler runs.

JavaScript Thenable Object

 

Naturally, the same is true if the first and second operations fulfill their promises but the third rejects its promise (or the handler starting it throws).

JavaScript Thenable Object

 

If all three operations succeed, the final fulfillment handler is run. If that handler throws, either directly or by calling a function that throws, the rejection handler gets run:

JavaScript Thenable Object

As you can see, a promise chain of this kind is very much like a try/ catch block around three synchronous operations (and some final code), like this:

// The same logic with synchronous operations
try {
    const firstResult = firstOperation();
    const secondResult = secondOperation(firstResult);
    const thirdResult = thirdOperation(secondResult * 2);
    // Use `thirdResult` here
} catch (error) {
    console.error(error);
}

Just as the separation of the main logic from the error logic is useful in try/ catch synchronous code, the separation of the main (fulfillment) logic from the error (rejection) logic in promise chains is useful.

 

Summary

A “promise” is an object or function with a then method whose behavior conforms to [the Promises/A+ specification]. While a “thenable” is an object or function that defines a then method.
So all promises are thenables, but not all thenables are promises. An object might define a method called then that doesn't work as defined for promises; that object would be a thenable, but not a promise.

 

References

Using Promises - JavaScript | MDN (mozilla.org)
Promise - JavaScript | MDN (mozilla.org)
Promise.resolve() - JavaScript | MDN (mozilla.org)

 

Olorunfemi Akinlua

Olorunfemi Akinlua

He is boasting over five years of experience in JavaScript, specializing in technical content writing and UX design. With a keen focus on programming languages, he crafts compelling content and designs user-friendly interfaces to enhance digital experiences across various domains. You can connect with him on his LinkedIn profile.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment