Modern JavaScript (ES6+)
Mind Map Summary
- Modern JavaScript (ES6/ES2015 and beyond)
- Variables:
let: Block-scoped variables. The modern replacement forvar.const: Block-scoped constants. The value cannot be reassigned.
- Functions:
- Arrow Functions (
=>): A concise syntax for functions. Lexically binds thethiskeyword, avoiding common bugs.
- Arrow Functions (
- Asynchronous Operations:
- Callbacks: Prone to “Callback Hell” (deeply nested, hard-to-read code).
- Promises: Represents the eventual success/failure of an async operation. Allows
.then()/.catch()chaining. async/await: Syntactic sugar over Promises. Writes async code that reads like synchronous code.
- Modules: Splitting code into separate files using
importandexport. - The Event Loop: Core mechanism for handling async operations without blocking the main thread.
- Variables:
Core Concepts
1. let and const vs. var
Before ES6, var was the only way to declare a variable. var is function-scoped, which can lead to confusing behavior (hoisting). let and const are block-scoped (they only exist within the nearest set of curly braces {}), which is more predictable.
- Best Practice: Always use
constby default. If you know you need to reassign the variable, uselet. Avoid usingvarin modern code.
2. Arrow Functions
The syntax is more concise ((a, b) => a + b), but the most important feature is how it handles this. In a traditional function, the value of this is determined by how the function is called. In an arrow function, this is determined by the surrounding scope (lexical binding). This solves a whole class of bugs, especially in event handlers and callbacks.
3. Asynchronous JavaScript & The Event Loop
JavaScript is single-threaded, meaning it can only do one thing at a time. The Event Loop allows it to handle long-running operations (like an API call) without freezing the UI.
- When an async operation (like
fetch) starts, it’s handed to the browser’s Web API. - The JavaScript call stack continues to run.
- When the Web API finishes, it places a callback function in the Callback Queue.
- The Event Loop checks if the call stack is empty. If it is, it pushes the first item from the queue onto the stack.
4. Promises and async/await
- Promises: An object representing the future result of an async operation. It can be
pending,fulfilled, orrejected. async/await: A layer on top of Promises. Anasyncfunction automatically returns a Promise.awaitpauses execution until a Promise settles, then resumes with the result.
Practice Exercise
Refactor callback-based asynchronous code (callback hell) to use Promises, and then again using async/await.
Answer
1. The “Bad” Code: Callback Hell
function getUser(id, callback) {
setTimeout(() => {
console.log("Fetched user");
callback(null, { id: id, name: "John Doe" });
}, 500);
}
function getPosts(userId, callback) {
setTimeout(() => {
console.log("Fetched posts");
callback(null, [
{ id: 1, title: "Post 1" },
{ id: 2, title: "Post 2" },
]);
}, 500);
}
function getComments(postId, callback) {
setTimeout(() => {
console.log("Fetched comments");
callback(null, [{ id: 101, text: "Great post!" }]);
}, 500);
}
// Callback Hell - The Pyramid of Doom
getUser(1, (err, user) => {
if (err) {
console.error(err);
} else {
getPosts(user.id, (err, posts) => {
if (err) {
console.error(err);
} else {
getComments(posts[0].id, (err, comments) => {
if (err) {
console.error(err);
} else {
console.log("Comments:", comments);
}
});
}
});
}
});
2. The “Better” Code: Using Promises
function getUserP(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Fetched user");
resolve({ id: id, name: "John Doe" });
}, 500);
});
}
function getPostsP(userId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Fetched posts");
resolve([{ id: 1, title: "Post 1" }]);
}, 500);
});
}
function getCommentsP(postId) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Fetched comments");
resolve([{ id: 101, text: "Great post!" }]);
}, 500);
});
}
// Promise Chaining
getUserP(1)
.then((user) => getPostsP(user.id))
.then((posts) => getCommentsP(posts[0].id))
.then((comments) => console.log("Comments:", comments))
.catch((err) => console.error("An error occurred:", err));
3. The “Best” Code: Using async/await
async function fetchPostComments(userId) {
try {
const user = await getUserP(userId);
const posts = await getPostsP(user.id);
const comments = await getCommentsP(posts[0].id);
console.log("Comments:", comments);
} catch (err) {
console.error("An error occurred:", err);
}
}
fetchPostComments(1);
Key Improvements
- Readability:
async/awaitreads top-to-bottom like synchronous code. - Error Handling: A single
try/catchblock replaces repetitive error checks in every callback. - Execution Flow: Promises avoid the “Pyramid of Doom” nesting.