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

And after the OK, the below

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
athen/catch/finallyhandler to resolve the promise they create means
that if you need to do a series of asynchronous operations that provide
promises/thenables, you can usethenon 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.



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.

If the first operation rejects its promise, though, that rejects the
promise from the firstthen, which rejects the promise from the
secondthen, which rejects the promise from thethirdthen, which
calls the rejection handler at the end. None of thethencallbacks get
called, because they were for fulfillment, not rejection.

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.

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

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:

As you can see, a promise chain of this kind is very much like
atry/catchblock 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
intry/catchsynchronous 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 athenmethod.
So all promises arethenables, but not allthenables are promises. An
object might define a method calledthenthat doesn’t work as defined
for promises; that object would be athenable, but not a promise.
References
Using Promises - JavaScript | MDN
(mozilla.org)
Promise - JavaScript | MDN
(mozilla.org)
Promise.resolve() - JavaScript | MDN
(mozilla.org)

