Table of Contents
Introduction..........................................................4
Synchronous vs asynchronous programming.........................4
What it means.......................................................................... 4
Synchronous.................................................................................4
Asynchronous...............................................................................4
Why JS cares........................................................................... 4
How it works (high level)............................................................4
Quick demo (order matters).........................................................5
When to use which.................................................................... 5
Callbacks & callback hell.............................................5
Callback basics.........................................................................5
Callback hell (aka pyramid of doom)..............................................5
Problems:...........................................................................................6
Workarounds.......................................................................................6
Promises: resolve, reject, chaining....................................6
Concepts.................................................................................6
Basic example......................................................................................6
Chaining.................................................................................7
Useful static helpers...................................................................7
Error propagation......................................................................7
Async/await syntax...................................................7
Syntactic sugar.........................................................................7
Example.............................................................................................8
Key points...........................................................................................8
When not to use....................................................................................8
Fetch API for HTTP requests.........................................9
Basics.................................................................................... 9
1
GET example.......................................................................................9
POST JSON example (async/await)...........................................................9
Important gotchas.................................................................................9
Advanced............................................................................................9
JSON handling in JavaScript.......................................10
Serializing.............................................................................10
Parsing.................................................................................10
Pitfalls................................................................................. 10
Example...........................................................................................10
Error handling in async code........................................11
Patterns................................................................................ 11
Graceful handling....................................................................11
Uncaught rejections.................................................................11
Example helper (fetch with error wrapper)................................................11
Retries + backoff....................................................................11
ES modules: import, export, default export.........................12
Syntax..................................................................................12
Re-exporting..........................................................................12
Dynamic import......................................................................12
Notes................................................................................... 12
Best practice..........................................................................13
Modularizing JavaScript code......................................13
Goals................................................................................... 13
Structure example...................................................................13
Patterns................................................................................ 13
Bundlers vs native modules.......................................................13
Encapsulation.........................................................................14
Working with external APIs........................................14
2
Steps before coding.................................................................14
Authentication........................................................................14
CORS.................................................................................. 14
Pagination.............................................................................14
Rate limiting........................................................14
Example: authenticated paginated fetch + retry...............................15
Security best practices..............................................................15
Testing.................................................................................15
Quick best-practices & checklist....................................16
3
MODULE 7: Advanced JavaScript
Introduction
Advanced JavaScript goes beyond the basics of variables, loops, and functions, diving into
the powerful features that make JavaScript one of the most versatile programming languages.
It covers concepts like asynchronous programming with callbacks, promises, and
async/await, which allow developers to handle real-time operations such as API calls and
event-driven tasks. Advanced topics also include closures, prototypes, the event loop, and
execution context, which give developers deep insight into how JavaScript works under the
hood. Modern features such as ES6+ modules, destructuring, spread/rest operators, arrow
functions, and classes improve code readability and maintainability. Additionally, advanced
JavaScript emphasizes modular coding practices, DOM manipulation for dynamic user
interfaces, error handling, and working with external APIs to build interactive and scalable
web applications. Mastering these concepts equips developers with the skills to write cleaner,
more efficient, and production-ready code.
Synchronous vs asynchronous programming
What it means
Synchronous code runs top-to-bottom and blocks subsequent steps until the current
operation completes. Example: heavy computation or a blocking IO call would
prevent anything else from running.
Asynchronous code schedules work to happen later (non-blocking). The engine
continues executing the next lines while the async work proceeds in the background
(I/O, timers, network).
Why JS cares
JavaScript in the browser is single-threaded (UI thread + event loop). If you run long
synchronous code, the UI freezes. Asynchrony keeps UI responsive.
How it works (high level)
The event loop coordinates:
o The call stack runs current functions.
o Web APIs / timers / network do work in background.
o Completed async work queues callbacks to the task queues.
4
o Microtask queue (promises) runs between macrotasks (setTimeout, events).
Important: Promises (then/catch) callbacks run as microtasks, which execute before
the next macrotask.
Quick demo (order matters)
[Link]('start');
setTimeout(() => [Link]('timeout 0'), 0);
[Link]().then(() => [Link]('promise'));
[Link]('end');
// Output:
// start
// end
// promise
// timeout 0
Explanation: promise is a microtask and runs before setTimeout (a macrotask).
When to use which
Synchronous for quick, deterministic logic.
Asynchronous for I/O, network requests, timers, large computations (or offload to
Web Worker).
Callbacks & callback hell
Callback basics
A callback is a function passed as an argument and executed later:
function doWork(cb) {
setTimeout(() => { cb(null, 'done'); }, 500);
}
doWork((err, result) => {
if (err) [Link](err);
else [Link](result);
});
5
Callback hell (aka pyramid of doom)
Nested callbacks for sequential async tasks quickly become unreadable:
login(user, pass, (err, userInfo) => {
if (err) return handle(err);
fetchProfile([Link], (err, profile) => {
if (err) return handle(err);
fetchPermissions(profile, (err, perms) => {
if (err) return handle(err);
// ...deep nesting
});
});
});
Problems:
Hard to read & maintain
Error handling duplication
Hard to compose tasks or run them in parallel
Stack traces get confusing
Workarounds
Use named functions instead of anonymous nested ones (improves readability).
Convert callback-based APIs to Promises (promisify) and use Promise chaining or
async/await.
Promises: resolve, reject, chaining
Concepts
A Promise represents a value that may be available now, later, or never.
States: pending → fulfilled (resolved) OR rejected.
The executor new Promise((resolve, reject) => { ... }) runs immediately.
Basic example
const p = new Promise((resolve, reject) => {
setTimeout(() => {
if ([Link]() > 0.2) resolve('ok');
else reject(new Error('failed'));
6
}, 300);
});
[Link](value => {
[Link]('success', value);
}).catch(err => {
[Link]('error', err);
}).finally(() => {
[Link]('done');
});
Chaining
Each .then() returns a new Promise. Returning a value continues the chain; returning a
Promise waits for it.
fetch('/api/user') // returns Promise<Response>
.then(res => [Link]()) // returns Promise<object>
.then(user => fetch(`/api/${[Link]}/orders`))
.then(res => [Link]())
.then(orders => [Link](orders))
.catch(err => [Link]('any error from above', err));
Useful static helpers
[Link]([p1,p2]) — resolves when all resolve, rejects if any reject.
[Link]([p1,p2]) — wait for all, get results even if some fail.
[Link]([p1,p2]) — resolves/rejects as soon as first settles.
[Link]([p1,p2]) — resolves with first fulfilled, rejects if all reject.
Error propagation
Throwing inside .then() causes rejection for the next .catch().
Avoid mixing callback-style error handling with Promise rejection; prefer .catch().
Async/await syntax
Syntactic sugar
async function implicitly returns a Promise.
7
await pauses execution inside the async function until the awaited Promise resolves
(non-blocking the whole thread — other tasks still run).
Example
async function loadUserAndOrders() {
try {
const resUser = await fetch('/api/user');
if (![Link]) throw new Error('User fetch failed');
const user = await [Link]();
// parallel: start both requests before awaiting
const ordersPromise = fetch(`/api/${[Link]}/orders`);
const messagesPromise = fetch(`/api/${[Link]}/messages`);
const [ordersRes, messagesRes] = await [Link]([ordersPromise, messagesPromise]);
const orders = await [Link]();
const messages = await [Link]();
return { user, orders, messages };
} catch (err) {
// handle or rethrow
[Link]('error', err);
throw err;
}
}
Key points
await only works inside async functions (note: modern modules support top-level
await).
Use [Link] for parallelism. Avoid await inside loops if you want concurrency:
// bad — sequential
for (const id of ids) {
await doFetch(id);
}
// good — parallel
await [Link]([Link](id => doFetch(id)));
When not to use
For fire-and-forget tasks where you don't need to await completion (but ensure errors
are handled).
8
Fetch API for HTTP requests
Basics
fetch(url, options) returns a Promise that resolves to a Response.
[Link] indicates 2xx status. [Link]() returns a Promise for parsed JSON.
GET example
fetch('/api/data')
.then(res => {
if (![Link]) throw new Error(`HTTP ${[Link]}`);
return [Link]();
})
.then(data => [Link](data))
.catch(err => [Link]('network or parse error', err));
POST JSON example (async/await)
async function postData(url, payload) {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: [Link](payload),
});
if (![Link]) throw new Error(`HTTP ${[Link]}`);
return [Link]();
}
Important gotchas
fetch rejects only on network errors; HTTP 4xx/5xx do NOT reject. Always check
[Link].
To abort requests, use AbortController:
const controller = new AbortController();
fetch('/api', { signal: [Link] });
// [Link]() to cancel
Handle timeouts by combining AbortController with setTimeout.
9
Advanced
fetch can stream responses via [Link]() (useful for large responses).
Use mode, credentials, cache, etc. in options when needed.
JSON handling in JavaScript
Serializing
[Link](value[, replacer, space])
o space is indentation (e.g., 2).
o Functions, undefined, and symbols are omitted (or converted in arrays to null).
o Circular references throw TypeError.
Parsing
[Link](text[, reviver])
o reviver lets you convert strings to Dates or other types:
const obj = [Link](text, (k, v) => {
if (typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(v)) return new Date(v);
return v;
});
Pitfalls
Date becomes string in JSON; reconstruct manually.
Big integers beyond Number.MAX_SAFE_INTEGER lose precision (use strings or
BigInt + custom serialization).
Always try/catch around [Link] for malformed input.
Example
try {
const obj = [Link](userInput);
} catch (err) {
[Link]('Invalid JSON', err);
}
10
Error handling in async code
Patterns
Promises: use .catch() at the end of chains.
async/await: wrap await calls in try/catch.
Always rethrow if you can't handle the error at the current level.
Graceful handling
Normalize errors so upper layers can display consistent messages (create custom
ApiError with status/res).
For UI, show friendly messages and fallback UI.
Uncaught rejections
Unhandled Promise rejections may be logged/warned and can terminate Node
processes in future—always attach .catch() or try/catch.
Example helper (fetch with error wrapper)
class ApiError extends Error {
constructor(message, response) { super(message); [Link] = response; }
}
async function safeFetch(url, opts) {
const res = await fetch(url, opts);
const text = await [Link](); // read body
let data;
try { data = text ? [Link](text) : null; } catch (err) { data = text; }
if (![Link]) throw new ApiError(`HTTP ${[Link]}`, { status: [Link], body: data });
return data;
}
Retries + backoff
For transient network errors, implement retries with exponential backoff + jitter.
Avoid retrying on client errors (4xx) unless specific (e.g., rate-limited 429 often needs
wait).
11
ES modules: import, export, default export
Syntax
Named export:
// [Link]
export function add(a,b){ return a+b; }
export const PI = 3.14;
// [Link]
import { add, PI } from './[Link]';
Default export:
// [Link]
export default function greet(name){ return `Hi ${name}`; }
// [Link]
import greet from './[Link]';
You can mix:
export default class A {}
export const x = 1;
Re-exporting
export * from './[Link]';
export { special } from './[Link]';
Dynamic import
Load modules lazily:
[Link]('click', async () => {
const module = await import('./[Link]');
[Link]();
});
Notes
In browsers, use <script type="module"> or serve as modules.
File extensions (.js) are usually required for native browser imports.
12
Top-level await is allowed in modules (modern browsers & Node with ESM).
Best practice
Prefer named exports for clarity and better tooling / tree-shaking.
Use default export when module only exposes one primary thing.
Modularizing JavaScript code
Goals
Single responsibility per module
Small, testable units
Clear public API (export surface)
No global variables
Structure example
/src
/api
[Link] // re-exports api functions
[Link]
[Link]
/ui
[Link]
[Link]
[Link]
[Link] // app bootstrap
Patterns
Use an [Link] in folders to re-export useful functions: export * from './[Link]';
export * from './[Link]'.
Keep side-effects out of modules (pure functions easier to test). If module must run
side-effects, explicitly provide init().
Bundlers vs native modules
For broad browser compatibility or advanced features (assets, transpilation), use
bundlers (Webpack, Rollup, Vite). For modern browsers, native ESM is fine.
13
Encapsulation
Hide internals: export only what’s needed. Internal helpers stay non-exported.
Working with external APIs
Steps before coding
1. Read API docs (endpoints, params, auth method, rate limits).
2. Get a test key / sandbox environment.
3. Test endpoints with Postman or cURL.
Authentication
Common types: API key, Bearer token (OAuth2), Basic auth.
Never store secret keys in client-side code — use a server to keep secrets and proxy
requests if necessary.
CORS
Browsers require the server to allow cross-origin requests (CORS headers). If you hit
a CORS error, the API or a proxy must be configured.
Pagination
APIs return paginated results (page/cursor). Example pattern using page:
async function fetchAllPages(url) {
let page = 1, all = [];
while (true) {
const res = await fetch(`${url}?page=${page}`);
const { items, nextPage } = await [Link]();
[Link](...items);
if (!nextPage) break;
page = nextPage;
}
return all;
}
Rate limiting
Respect Retry-After and 429 responses. Implement exponential backoff and caching
where reasonable.
14
Example: authenticated paginated fetch + retry
async function apiGet(url, token, maxRetries = 3) {
let attempt = 0;
while (attempt <= maxRetries) {
try {
const res = await fetch(url, {
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/json'
}
});
if ([Link] === 429) {
const wait = [Link]('Retry-After') || (2 ** attempt) * 1000;
await new Promise(r => setTimeout(r, wait));
attempt++;
continue;
}
if (![Link]) throw new Error(`HTTP ${[Link]}`);
return await [Link]();
} catch (err) {
if (attempt === maxRetries) throw err;
attempt++;
await new Promise(r => setTimeout(r, 2 ** attempt * 200));
}
}
}
Security best practices
Keep tokens out of client-side source; forward requests through a backend when
secrets are required.
Use HTTPS.
Validate and sanitize data received from external APIs.
Testing
Mock API responses for unit tests (tools: msw, sinon, jest mocks).
Use integration tests in a sandbox environment.
15
Quick best-practices & checklist
Prefer async/await for readability but use [Link] for parallel tasks.
Always check HTTP response status with fetch (don’t rely on rejection).
Normalize and centralize API error handling (ApiClient layer).
Use AbortController to cancel long-running requests.
Keep modules small and export explicit named APIs.
Do not store secrets in client code; proxy via server for sensitive requests.
Handle JSON parsing errors and validate incoming data.
Implement exponential backoff + jitter for retries.
Monitor unhandled promise rejections in development (attach .catch())
16