In Part 1 we covered the syntactic foundations of modern JavaScript. This second part dives into asynchronous programming and code organization — promises, async/await, and modules — which are essential for building real applications.
#Promises
A promise represents a value that may be available now, later, or never. It's the standard way to handle asynchronous operations like network requests.
1const fetchUser = (id) => {
2 return new Promise((resolve, reject) => {
3 setTimeout(() => {
4 if (id > 0) {
5 resolve({ id, name: "Ada" });
6 } else {
7 reject(new Error("Invalid id"));
8 }
9 }, 1000);
10 });
11};
Consume a promise with .then(), .catch(), and .finally():
1fetchUser(1)
2 .then((user) => console.log(user.name))
3 .catch((error) => console.error(error.message))
4 .finally(() => console.log("Done"));
#Chaining
Each .then() returns a new promise, so you can chain operations sequentially. Returning a value passes it to the next handler; returning a promise waits for it to settle.
1fetchUser(1)
2 .then((user) => fetchPosts(user.id))
3 .then((posts) => console.log(posts.length))
4 .catch((error) => console.error(error));
#async / await
async/await is syntactic sugar over promises that lets you write asynchronous code that reads like synchronous code.
1async function loadUser(id) {
2 try {
3 const user = await fetchUser(id);
4 const posts = await fetchPosts(user.id);
5 return { user, posts };
6 } catch (error) {
7 console.error("Failed to load:", error.message);
8 }
9}
An async function always returns a promise, and await pauses execution until the awaited promise settles.
#Running in Parallel
Awaiting sequentially when operations are independent wastes time. Use Promise.all to run them concurrently.
1async function loadDashboard(userId) {
2 const [profile, settings, notifications] = await Promise.all([
3 fetchProfile(userId),
4 fetchSettings(userId),
5 fetchNotifications(userId),
6 ]);
7 return { profile, settings, notifications };
8}
Related combinators:
Promise.all— resolves when all settle, rejects on the first rejection.Promise.allSettled— waits for all and reports each outcome.Promise.race— settles as soon as the first promise settles.Promise.any— resolves with the first fulfilled promise.
#ES Modules
ES Modules (ESM) provide a standardized way to split code across files using import and export.
#Named Exports
1// math.js
2export const PI = 3.14159;
3export function square(n) {
4 return n * n;
5}
1// app.js
2import { PI, square } from "./math.js";
3console.log(square(PI));
#Default Exports
A module can have one default export, useful for a single primary value.
1// logger.js
2export default function log(message) {
3 console.log(`[LOG] ${message}`);
4}
1// app.js
2import log from "./logger.js";
3log("Started");
#Renaming and Namespace Imports
1import { square as sq } from "./math.js";
2import * as math from "./math.js";
3
4console.log(sq(4));
5console.log(math.PI);
#Dynamic Imports
You can load modules on demand, returning a promise. This enables code splitting and lazy loading.
1async function loadChart() {
2 const { renderChart } = await import("./chart.js");
3 renderChart();
4}
#Wrapping Up
Promises and async/await tame asynchronous flows, while ES Modules keep growing codebases organized and reusable. Together they underpin virtually every modern JavaScript project. In Part 3 we'll explore iterators, generators, and newer additions like optional chaining and nullish coalescing.