A network call takes two hundred milliseconds to answer. JavaScript itself cannot wait: it runs its statements through without ever pausing. So it needed a way to declare what should happen once the answer arrives, without freezing the page.
A promise is that in-between object. It stands for a result that does not exist yet, and may never exist at all.
Definition
A Promise is an object standing for the outcome of an asynchronous operation: a value delivered later, or a failure. You never take that value directly, you register in advance the work to run on it.
const readInvoice = async (id) => {
if (id !== 42) throw new Error("Invoice not found");
return { id, amount: 19 };
};
readInvoice(42)
.then((f) => console.log(f.amount)) // 19
.catch((err) => console.log(err.message))
.finally(() => console.log("done"));then receives the value on success, catch receives the Error on failure, finally runs in both cases. All three return a new promise, which is what makes chaining possible.
The three states
| State | What it means | What causes it |
|---|---|---|
| Pending | The result is not known | The starting state |
| Fulfilled | A value is available | A resolve or a return |
| Rejected | An error is available | A reject or a throw |
The move is final: a fulfilled promise never goes back to pending and its result never changes again.
A promise starts when it is created, not when it is awaited. Calling an asynchronous function kicks off the work right away, even if the await shows up ten lines later.
await, the same thing more readably
Inside an async function, await pauses what follows until the promise settles, then hands over its value. Failures become catchable again by an ordinary try.
async function showInvoice(id) {
try {
const invoice = await readInvoice(id);
console.log(invoice.amount);
} catch (err) {
console.log("Unavailable:", err.message);
}
}The code reads top to bottom although it does not run in that order, and the gain becomes considerable across five chained calls.
Waiting on several promises
| Method | What it hands back |
|---|---|
Promise.all | Every value, or the first failure |
Promise.allSettled | The outcome of each one, never failing |
Promise.race | The first to settle, success or failure |
Promise.any | The first to fulfill, failures ignored |
async function loadEverything() {
const [profile, invoices] = await Promise.all([
readProfile(),
readInvoices(),
]);
console.log(profile, invoices); // both calls ran together
}Frequently asked questions
Is new Promise still worth writing?
Rarely. Any function declared async already returns a promise, and modern interfaces produce one on their own. The constructor is now only there to wrap an older callback-based interface, such as setTimeout.
What happens when nobody catches a rejection?
The failure travels up to the engine as an unhandled rejection: in a browser it ends up in the console, under Node.js it stops the process as of version 15. A promise with no catch is therefore a real risk, not a warning.
Chain the await calls or reach for Promise.all?
It depends on how the calls relate. If the second one needs the first one's result, the sequence is unavoidable. If they are independent, starting them together divides the wait by their count, a reflex drilled in the JavaScript course.