Six Things You Might Not Know About Promises

Share this article

Promises
are a simple concept, and even if you haven’t had a chance to use them, you may have already read up on them. They are a valuable construct that enables asynchronous code to be structured in a more readable fasion, rather than as a mess of nested anonymous functions. This article touches on six things that you might not know about promises. Before diving into the list, here is a quick reminder of what JavaScript promises look like:
var p = new Promise(function(resolve, reject) {
resolve("hello world");
});

p.then(function(str) {
alert(str);
});

1. then() Returns a Forked Promise

What is the difference between the following two blocks of code?
// Exhibit A
var p = new Promise(/*...*/);
p.then(func1);
p.then(func2);
// Exhibit B
var p = new Promise(/*...*/);
p.then(func1)
.then(func2);
If you think both code blocks are equivalent, you may be thinking that promises are nothing more than one-dimensional arrays of callbacks. However, that is not actually the case. Every call to then() returns a forked promise. So, in Exhibit A, if func1() throws an exception, func2() will still be called as normal. In Exhibit B, if func1() throws an exception, func2() won’t be called, because the first call to then() returned a new promise, which was rejected due to the exception in func1(). The result is that func2() is skipped over. The takeaway: promises can fork into multiple paths like a complex flow chart.

2. Callbacks Should Pass Results

What gets alerted when you run the following code?
var p = new Promise(function(resolve, reject) {
resolve("hello world");
});

p.then(function(str) {})
.then(function(str) {
alert(str);
});
The alert in the second then() does not display anything. This is because callbacks, in the context of promises, aren’t so much callbacks as they are transformers of results. The promise expects your callback to either return the same result or a replacement, which then gets passed on to the next callback. This idea is similar to using adapters to transform a result, as shown in the following example.
var feetToMetres = function(ft) { return ft*12*0.0254 };

var p = new Promise(/*...*/);

p.then(feetToMetres)
.then(function(metres) {
alert(metres);
});

3. Only Exceptions From Previous Levels are Caught

What is the difference between these two code blocks:
// Exhibit A
new Promise(function(resolve, reject) {
resolve("hello world");
})
.then(
function(str) {
throw new Error("uh oh");
},
undefined
)
.then(
undefined,
function(error) {
alert(error);
}
);
// Exhibit B
new Promise(function(resolve, reject) {
resolve("hello world");
})
.then(
function(str) {
throw new Error("uh oh");
},
function(error) {
alert(error);
}
);
In Exhibit A, when an exception is thrown in the first then(), it is caught in the second then() and “uh oh” is alerted. This follows the rule that only exceptions from previous levels are caught. In Exhibit B, the callback and the error callback are at the same level, meaning when the exception is thrown in the callback, it won’t be caught. In fact, Exhibit B’s error callback will only execute if the promise is in a rejected state or if the promise itself throws an exception.

4. Errors Can Be Recovered From

Inside an error callback, if you don’t re-throw the error, the promise will assume that you’ve recovered from the error and will revert to the resolved state. In the following example, “I am saved” is displayed because the error callback in the first then() did not re-throw the exception.
var p = new Promise(function(resolve, reject) {
reject(new Error("pebkac"));
});

p.then(
undefined,
function(error) { }
)
.then(
function(str) {
alert("I am saved!");
},
function(error) {
alert("Bad computer!");
}
);
Promises can be seen as layers on an onion. Each then() adds another layer to the onion. Each layer represents one activity that can be handled. After that layer is over, the result is assumed to be fixed and ready for the next layer.

5. Promises Can Be Paused

Just because you are already executing inside a then() function, doesn’t mean you can’t pause it to complete something else first. To pause the current promise, or to have it wait for the completion of another promise, simply return another promise from within then().
var p = new Promise(/*...*/);

p.then(function(str) {
if(!loggedIn) {
return new Promise(/*...*/);
}
})
.then(function(str) {
alert("Done.");
})
In the previous code sample, the alert will not be shown until the new promise has been resolved. This is a convenient way to introduce additional dependencies inside an existing asynchonous code path. For example, you may find that the user session has timed out and you may want to initiate a secondary login before continuing on with the previous code path.

6. Resolved Promises Don’t Execute Immediately

What gets alerted when you run the following code?
function runme() {
var i = 0;

new Promise(function(resolve) {
resolve();
})
.then(function() {
i += 2;
});
alert(i);
}
You may think it will alert 2, since the promise is resolved immediately and the then() function is executed immediately (synchronously). However, the promise specification requires all calls to be forcefully asynchronous in an effort to be uniform. Therefore, the alert is called before the value of i is modified. Links: Download various implementations of the Promise/A+ API.

Frequently Asked Questions about JavaScript Promises

What is the execution order of JavaScript Promises?

The execution order of JavaScript Promises is determined by the event loop and microtask queue. When a Promise is created, its executor function runs immediately. The Promise callbacks (.then/catch/finally) are pushed into the microtask queue, which is processed after the current execution context is finished and before control is returned to the event loop. This means that Promises are always resolved asynchronously, even if the Promise is already settled.

How does error handling work in Promises?

Error handling in Promises is done using the .catch() method. If an error is thrown in the executor function or in a .then() callback, it will be caught by the nearest .catch() handler down the chain. If no .catch() handler is provided, the error will be unhandled, which can lead to uncaught exception errors.

Can a Promise be cancelled?

JavaScript Promises cannot be cancelled once they are created. This is one of the limitations of Promises. However, there are workarounds to simulate cancellation, such as using a flag to ignore the result of a Promise.

What is Promise chaining?

Promise chaining is a technique where the return value of the first .then() function becomes the input for the next .then() function. This allows for sequential execution of asynchronous operations. Each .then() returns a new Promise, allowing the chain to continue.

How can multiple Promises be handled simultaneously?

JavaScript provides two methods to handle multiple Promises simultaneously: Promise.all() and Promise.race(). Promise.all() waits for all Promises to resolve and returns an array of their results. Promise.race() returns as soon as the first Promise resolves or rejects.

What is the difference between Promise.resolve() and Promise.reject()?

Promise.resolve() and Promise.reject() are utility methods to create a Promise that is already settled. Promise.resolve() creates a Promise that is resolved with the given value. Promise.reject() creates a Promise that is rejected with the given reason.

What is the use of the finally() method in Promises?

The finally() method is used to specify callbacks that should be executed regardless of whether the Promise was resolved or rejected. This is useful for cleanup tasks, such as closing database connections.

How can Promises be used with async/await?

The async/await syntax is a way to work with Promises in a more synchronous-looking manner. An async function always returns a Promise, and the await keyword can be used to wait for a Promise to resolve.

What is Promise.allSettled()?

Promise.allSettled() is a method that waits for all Promises to settle, either resolved or rejected. It returns an array of objects that describe the outcome of each Promise, which is useful when you want to perform actions regardless of whether the Promises were fulfilled or rejected.

What happens when a Promise is rejected?

When a Promise is rejected, it moves to the “rejected” state and the rejection reason is passed to the .catch() handler. If no .catch() handler is provided, the rejection is considered unhandled, which can lead to uncaught exception errors.

Dmitri LauDmitri Lau
View Author

Dmitri Lau is a freelance Ajax developer with a penchant for statistical analysis. When not improving a meta template engine's relative response yield, yawning uninterruptedly every night has become a norm which he hopes will soon be over when his Hong Kong based startup picks up.

Promises
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week