A promise represents a result that arrives later
JavaScript does not pause an entire program while it waits for a timer, a network response, or another asynchronous task. Instead, an operation can return a promise: an object representing a result that may become available later. A promise can fulfill with a value or reject with a reason. Code that needs the outcome must handle one of those possibilities rather than assume the value is ready immediately.
Try the examples in a modern browser console or a current Node.js environment. They use setTimeout to stand in for work that takes time; they make no network requests. First, define a reusable function: function makeValue(value, ms) { return new Promise(resolve => { setTimeout(() => resolve(value), ms); }); } Calling makeValue("ready", 100) returns a promise immediately. After roughly 100 milliseconds, that promise fulfills with the string "ready".
A common mistake is to treat the promise as the finished value. For example, const result = makeValue("ready", 100); assigns a promise to result, not the string. To work with the string, attach a handler or await the promise inside an async function.
Sequence dependent steps with then
The then method registers a function to run after a promise fulfills. Paste this after defining makeValue: makeValue("first", 100).then(value => { console.log(value); return makeValue(value + " second", 100); }).then(value => { console.log(value); }); The first handler logs "first" and returns another promise. The next handler waits for that returned promise before logging "first second".
Returning the next promise is what connects the two steps. If the first handler calls makeValue but does not return it, the following handler cannot wait for its result. Likewise, returning an ordinary value from a handler passes that value to the next then handler. This makes a promise chain useful when each operation depends on the previous result.
Calling then also returns a new promise. That means a function can return the whole chain to its caller: function buildMessage() { return makeValue("hello", 100).then(value => value + " world"); } A caller can then use buildMessage().then(console.log) and receive "hello world". Without the return in buildMessage, its caller would receive undefined rather than a promise it could wait for.
Use async/await to express the same sequence
An async function provides another way to write promise-based code. Inside it, await pauses that function until a promise settles; it does not block the rest of the program. Using the same makeValue function, try: async function buildMessage() { const first = await makeValue("hello", 100); const message = await makeValue(first + " world", 100); return message; } buildMessage().then(console.log); This logs "hello world" after the two waits complete.
An async function always returns a promise, even when its return statement names an ordinary value. In this example, return message fulfills the promise returned by buildMessage with that string. A caller must await buildMessage() from another async function or use then to obtain the string. If an async function has no return statement, its promise fulfills with undefined.
Place await where the result is needed, not automatically before every promise-producing call. The syntax helps make dependencies visible: the second makeValue call needs first, so it must follow the first await.
Handle failures at the boundary that can respond
A rejected promise skips fulfillment handlers until a rejection handler deals with it. With then, use catch on the returned chain: makeValue("start", 100).then(() => Promise.reject(new Error("Could not finish"))).catch(error => { console.log(error.message); }); This logs "Could not finish". A rejection from either the original operation or a later returned promise can reach that catch handler.
With async/await, a try/catch block often reads more naturally: async function runTask() { try { const value = await Promise.reject(new Error("Could not finish")); return value; } catch (error) { console.log(error.message); return "fallback"; } } runTask().then(console.log); This logs the error message and then "fallback". Returning a fallback turns the failure into a fulfilled result. If the function cannot meaningfully recover, it can let the rejection reach its caller instead.
When calling an async function, remember to handle its returned promise. A try/catch around an awaited call can do that; so can a catch handler on the returned promise. Otherwise, a failure may go unnoticed by the part of your application responsible for responding to it.
Run independent work together when appropriate
Sequential awaits are useful when one result determines the next step. If two operations do not depend on each other, starting both before waiting may avoid unnecessary waiting. Try: async function getPair() { const leftPromise = makeValue("left", 100); const rightPromise = makeValue("right", 100); const [left, right] = await Promise.all([leftPromise, rightPromise]); return left + " and " + right; } getPair().then(console.log); The result is "left and right". Both timers start before Promise.all waits for their results.
Promise.all fulfills with values in the same order as its input promises, regardless of which finishes first. It rejects if any input promise rejects. Use it when the whole group is needed; if each result needs separate handling, structure the work accordingly. The central choice is simple: await a result before starting the next operation when there is a dependency, or start independent operations first and then await their combined results.



