JavaScript: JavaScript Async Programming
Last updated: 2026-10-13
JavaScript is single-threaded — it can only do one thing at a time. But if a network request takes 3 seconds, should the page freeze for 3 seconds? Of course not. Async programming solves this: instead of waiting around, you take a number and come back when it's ready. Like a restaurant queue system — you don't stand at the kitchen door, you sit down and wait for your number to be called.
1. Sync vs Async
Synchronous: Code executes line by line — the next line waits for the current one to finish. Like standing in line — you wait until the person ahead of you is done.
Asynchronous: Certain operations (network requests, timers, file I/O) start without blocking subsequent code. When they finish, you're notified via a callback or Promise.
<script>
console.log('1');
setTimeout(function() {
console.log('2');
}, 1000);
console.log('3');
</script>
The output order is 1 → 3 → 2. setTimeout is asynchronous — its callback runs after 1 second without blocking console.log('3').
2. Callbacks
Callbacks are the most basic async pattern — pass a function as an argument, and it gets called when the async operation completes.
▶ Example: Basic Callback Usage
▶ Try it Yourself(1) Callback Hell
When async operations depend on each other in sequence, callbacks nest deeper and deeper — this is the infamous "Callback Hell."
<script>
getUser(function(user) {
getOrders(user.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
getItems(detail.itemId, function(items) {
console.log('4 levels deep — add more complexity and it becomes unreadable');
});
});
});
});
</script>
3. Promise
A Promise is an object that represents the eventual result of an async operation. It has three states:
| State | Description |
|---|---|
pending |
In progress, no result yet |
fulfilled |
Completed successfully, result available |
rejected |
Failed, error available |
Once a Promise transitions from pending to fulfilled or rejected, it never changes back — like an arrow once shot, it can't be recalled.
(1) Creating a Promise
<script>
const promise = new Promise(function(resolve, reject) {
// async operation
// call resolve(result) on success
// call reject(error) on failure
});
</script>
(2) then / catch / finally
<script>
promise
.then(function(result) { console.log(result); }) // runs on success
.catch(function(error) { console.error(error); }) // runs on failure
.finally(function() { console.log('Done'); }); // runs regardless
</script>
▶ Example: Simulating a Request with Promise
▶ Try it Yourself(3) Chaining
then itself returns a Promise, so you can chain calls — callback hell becomes a straight line.
<script>
fetchUser()
.then(function(user) { return fetchOrders(user.id); })
.then(function(orders) { return fetchDetail(orders[0].id); })
.then(function(detail) { console.log(detail); })
.catch(function(err) { console.error(err); });
</script>
catch at the end of the chain catches errors from any step — you don't need a catch after every then.
4. async / await
async/await is syntactic sugar over Promises — it makes async code look synchronous, greatly improving readability.
async: Declares an async function that automatically returns a Promiseawait: Pauses execution until the Promise resolves, then returns the result
▶ Example: Rewriting the Above Request with async/await
▶ Try it Yourselfasync/await turns "take a number and wait" into "stay in line without leaving" — the code flow is straight and readability skyrockets.
(1) Error Handling
async/await uses try/catch for error handling — the same pattern used with synchronous code.
<script>
async function loadAll() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id);
console.log(orders);
} catch (err) {
console.error('Something went wrong:', err);
}
}
</script>
5. Promise.all and Promise.race
| Method | Description |
|---|---|
Promise.all([p1, p2, p3]) |
Succeeds only if all succeed; fails as soon as one fails |
Promise.race([p1, p2, p3]) |
Uses the result of whichever settles first (success or failure) |
▶ Example: Concurrent Requests with Promise.all
▶ Try it YourselfPromise.all is like a group dinner — everyone must arrive before you eat. Promise.race is like a race — whoever finishes first wins. Use all for independent requests to speed things up, and race when you have multiple mirror sources and want the fastest one.
❓ FAQ
await be used in a regular function?await can only be used inside an async function. Using await in a regular function causes a syntax error. Top-level await is supported in modules, but browser support varies.Promise.all fails?Promise.all immediately rejects with the first error. Other Promises still execute, but their results are ignored. If you need "all settled regardless of outcome," use Promise.allSettled.async function return?async function always returns a Promise. Even if you return 42, it actually returns Promise.resolve(42). So you can use .then() to consume the result.📖 Summary
- Sync code blocks subsequent execution; async code doesn't — single-threaded can still "do multiple things"
- Callbacks are the most basic async pattern; deep nesting creates callback hell
- Promise has three states:
pending → fulfilledorpending → rejected— state transitions are irreversible then/catch/finallyconsume Promises; chaining solves callback hellasync/awaitis syntactic sugar over Promises;awaitcan only be used insideasyncfunctionsPromise.allwaits for all to succeed;Promise.racetakes the first to settle
📝 Exercises
- Basic: Use
Promise+setTimeoutto simulate a 2-second delay, then output "Done" on success. - Intermediate: Rewrite the above with
async/awaitand addtry/catcherror handling. - Challenge: Use
Promise.allto fire 3 simulated requests with different delays (1s/2s/3s) concurrently, then calculate and display the total elapsed time when all complete.