Free tools Windows power users keep installed
One-click scans. No signup required.
Use async/await when a dependent sequence is clearest as a series of steps; use .then() when a compact transformation pipeline reads better. Both use the same promise model. For independent work, start the operations before combining them with a method such as Promise.all()—choosing await alone does not make work concurrent.
How promises and async/await relate
async/await is syntax built on promises, not a replacement for them. An async function always returns a promise: it fulfills with the function’s return value, or rejects if the function throws. await lets code inside an async function wait for a promise’s settlement and use its fulfillment value directly. Top-level await is also available in module contexts that permit it. See MDN’s guides to async functions and await.
In a promise chain, each .then() returns a new promise. The next handler receives the prior handler’s return value; if that return value is itself a promise, the chain waits for it to settle before continuing. A rejection can propagate through the chain until a rejection handler handles it. MDN summarizes the relationship: “async/await has the same concurrency semantics as normal promise chains.” (MDN, Using promises.)
Which style fits the code?
| Situation | Promise chaining | async/await |
|---|---|---|
| Dependent steps | Return each value or promise from a handler so the next handler can use it. | Use sequential await statements to make the dependency visible as local steps. |
| Independent operations | Start the operations, then compose their promises, for example with Promise.all(). |
Start the operations first, then await a combined result such as Promise.all(). |
| Transformations | A short sequence of transformations can read naturally as a fluent pipeline. | Often easier to scan when the workflow needs local variables, branches, or several imperative steps. |
| Error handling | Use a rejection handler in .then() or add .catch() at the appropriate point. |
Use try/catch around awaited operations, or handle a promise before awaiting it. |
| Runtime behavior | Promise settlement and handler scheduling govern the behavior. | Uses the same promise concurrency semantics; syntax alone does not make it faster or more concurrent. |
For a short dependent sequence, choose the form that makes the data flow easiest to follow in the surrounding code. A chain is not inherently less readable, and async/await is not automatically the best fit for every promise-based operation.
#1 Best Overall
Equivalent examples for dependent work
In both versions, each operation depends on the result of the one before it: first obtain a URL, then fetch it, parse the response, and use the data. These examples follow MDN’s documented rewrite pattern.
Promise chain
function logIngredients() {
return doSomething()
.then((url) => fetch(url))
.then((response) => response.json())
.then((data) => {
listOfIngredients.push(data);
console.log(listOfIngredients);
});
}
async/await
async function logIngredients() {
const url = await doSomething();
const response = await fetch(url);
const data = await response.json();
listOfIngredients.push(data);
console.log(listOfIngredients);
}
In production code, decide whether the function should return the parsed data, check HTTP response status where relevant, and establish an intentional error-handling boundary. A successful fetch() promise does not by itself mean the HTTP response represents a successful status.
Rank #2
How to run independent promises concurrently
Separate await expressions run in sequence. That is appropriate when a later operation needs an earlier result, but it unnecessarily delays independent work if each operation can begin immediately. Start both first, then wait for the combined result:
const userPromise = getUser();
const settingsPromise = getSettings();
const [user, settings] = await Promise.all([userPromise, settingsPromise]);
The same principle applies to promise chains: concurrency depends on when operations start and how their promises are composed, not on whether the code uses await or .then().
Choose the aggregate by the result you need
Promise.all()fulfills with all results when every input fulfills, and rejects as soon as an input rejects. Other operations continue running; their outcomes are not included in the rejected aggregate, andPromise.all()does not cancel them.Promise.allSettled()lets the caller inspect each operation’s settled outcome, including both fulfillments and rejections.Promise.any()andPromise.race()are also composition options; choose among these methods based on the outcome semantics the program requires. MDN documents these methods in its Promise reference.
Handle failures without hiding them
In a chain, a rejection travels down the chain until a rejection handler handles it. A trailing .catch(handler) is shorthand for .then(undefined, handler), so it can handle a rejection from an earlier step. If the handler returns a value, the promise produced by .catch() fulfills with that value; if it throws or returns a rejected promise, the failure path continues.
In async code, a rejection from an awaited promise is thrown at the await expression and can be handled with try/catch. Put recovery close to the operation when it is specific to that operation; otherwise, let the error reach a deliberate boundary. Be careful with fallbacks: returning a fallback from a catch handler converts the resulting promise into a fulfilled one. For the documented mechanics, see MDN’s references for promise handling, Promise, and await.
Rank #4
Does await block JavaScript?
await suspends the continuation of its surrounding async function until the awaited value settles. It does not block the entire program: other asynchronous operations can continue while that function is waiting. What matters for concurrent work is whether those operations have been started before the code waits for their results.
Quick Recap
Best Value
A practical rule of thumb
- Use sequential
awaitwhen each step needs the previous result and named local values make the workflow easier to understand. - Use a promise chain when a short sequence of transformations or promise-returning handlers is clearer as a pipeline.
- Use
try/catchor chain rejection handlers at the point that matches the intended recovery boundary. - For independent work, start operations before awaiting or chaining their aggregate; select
Promise.all(),Promise.allSettled(),Promise.any(), orPromise.race()to match the result and failure behavior you need.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.
Recommended Free Tools

