📅 NGÀY 2: ES6+ NÂNG CAO - CÔNG CỤ QUYỀN NĂNG CHO REACT
📍 Phase 1, Week 1, Day 2 of 75
⏱️ Thời lượng: 3-4 giờ (bao gồm breaks)
🎯 Mục tiêu học tập (5 phút)
Sau bài học hôm nay, bạn sẽ:
- [ ] Hiểu và vận dụng thành thạo Promises và async/await (concepts - chưa dùng thực tế)
- [ ] Làm chủ ES6 Modules - import/export patterns cho React projects
- [ ] Sử dụng thành thạo Optional chaining (?.) và Nullish coalescing (??)
- [ ] Nắm vững các Array/Object methods nâng cao (find, some, every, entries, keys, values)
🤔 Kiểm tra đầu vào (5 phút)
Trước khi bắt đầu, hãy tự hỏi mình:
- Destructuring:
const { name, age } = user- Bạn có hiểu cách này hoạt động không? - Spread operator:
const newArr = [...oldArr, newItem]- Tại sao cần dấu...? - Arrow functions:
const double = x => x * 2- Khác gì vớifunction double(x) { return x * 2 }?
💡 Nếu chưa vững, hãy xem lại Ngày 1 trước khi tiếp tục!
📖 PHẦN 1: GIỚI THIỆU KHÁI NIỆM (30 phút)
1.1 Vấn Đề Thực Tế
Khi xây dựng ứng dụng React, bạn sẽ thường xuyên gặp những tình huống này:
Scenario 1: Code "callback hell"
// ❌ Code khó đọc, khó maintain
fetchUser(userId, function (user) {
fetchPosts(user.id, function (posts) {
fetchComments(posts[0].id, function (comments) {
console.log(comments);
// 😱 "Pyramid of Doom"
});
});
});Scenario 2: Import/Export lộn xộn
// ❌ Không biết import từ đâu
import Something from "./utils";
import AnotherThing from "./helpers";
import YetAnother from "./tools";
// 🤔 File nào có gì?Scenario 3: Null/Undefined checks rối rắm
// ❌ Code dài dòng, dễ sai
if (user && user.profile && user.profile.address && user.profile.address.city) {
console.log(user.profile.address.city);
}1.2 Giải Pháp
ES6+ cung cấp các công cụ mạnh mẽ để giải quyết các vấn đề trên:
✅ Promises & async/await - Code bất đồng bộ dễ đọc như code đồng bộ
✅ ES6 Modules - Tổ chức code rõ ràng, tái sử dụng cao
✅ Optional chaining (?.) - Truy cập nested properties an toàn
✅ Nullish coalescing (??) - Default values thông minh hơn
1.3 Mental Model
┌─────────────────────────────────────────┐
│ ES6+ MODERN JAVASCRIPT TOOLS │
├─────────────────────────────────────────┤
│ │
│ Async Programming │
│ ┌──────────────────────────┐ │
│ │ Callback → Promise → │ │
│ │ async/await │ │
│ │ (từ khó → dễ đọc) │ │
│ └──────────────────────────┘ │
│ │
│ Code Organization │
│ ┌──────────────────────────┐ │
│ │ import/export → │ │
│ │ Module system │ │
│ │ (code gọn gàng) │ │
│ └──────────────────────────┘ │
│ │
│ Safe Access │
│ ┌──────────────────────────┐ │
│ │ ?. (optional chain) │ │
│ │ ?? (nullish coalesce) │ │
│ │ (tránh runtime errors) │ │
│ └──────────────────────────┘ │
│ │
│ Data Processing │
│ ┌──────────────────────────┐ │
│ │ find, some, every... │ │
│ │ (xử lý data hiệu quả) │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────┘Analogy dễ hiểu:
🍳 Nấu ăn theo recipe:
- Callback = Gọi điện hỏi từng bước (chậm, dễ quên)
- Promise = Có recipe viết sẵn (rõ ràng hơn)
- Async/await = Có người hướng dẫn từng bước (dễ nhất!)
1.4 Hiểu Lầm Phổ Biến
❌ SAI #1: "async/await là cách viết khác của Promise"
✅ ĐÚNG: async/await là syntax sugar giúp VIẾT code với Promise dễ hơn
❌ SAI #2: "Optional chaining (?.) và OR (||) giống nhau"
✅ ĐÚNG: ?. check null/undefined, || check falsy (bao gồm 0, '', false)
❌ SAI #3: "Import default và named import dùng thế nào cũng được"
✅ ĐÚNG: Có quy ước rõ ràng - default cho main export, named cho utilities
💻 PHẦN 2: LIVE CODING (45 phút)
Demo 1: Promises & async/await Basics ⭐
A. Hiểu Promise từ đầu
// ========================================
// DEMO: Tạo và sử dụng Promise
// ========================================
// ❌ CÁC SAI: Callback Hell
console.log("=== ❌ Callback Pattern (Khó đọc) ===");
function fetchUserCallback(userId, callback) {
setTimeout(() => {
const user = { id: userId, name: "John Doe" };
callback(user);
}, 1000);
}
function fetchPostsCallback(userId, callback) {
setTimeout(() => {
const posts = [
{ id: 1, title: "Post 1", userId },
{ id: 2, title: "Post 2", userId },
];
callback(posts);
}, 1000);
}
// Sử dụng - KHỦNG KHIẾP!
fetchUserCallback(1, (user) => {
console.log("User:", user);
fetchPostsCallback(user.id, (posts) => {
console.log("Posts:", posts);
// Nếu cần fetch thêm → càng lồng sâu hơn 😱
});
});
// ✅ CÁCH ĐÚNG: Promise Pattern
console.log("\n=== ✅ Promise Pattern (Dễ đọc hơn) ===");
function fetchUserPromise(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const user = { id: userId, name: "John Doe" };
resolve(user); // Thành công
// reject(new Error('User not found')); // Thất bại
}, 1000);
});
}
function fetchPostsPromise(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const posts = [
{ id: 1, title: "Post 1", userId },
{ id: 2, title: "Post 2", userId },
];
resolve(posts);
}, 1000);
});
}
// Sử dụng - GỌN GÀNG HƠN!
fetchUserPromise(1)
.then((user) => {
console.log("User:", user);
return fetchPostsPromise(user.id); // Chaining
})
.then((posts) => {
console.log("Posts:", posts);
})
.catch((error) => {
console.error("Error:", error);
});
// 🎯 CÁCH TỐT NHẤT: async/await
console.log("\n=== 🎯 async/await Pattern (DỄ NHẤT!) ===");
async function getUserAndPosts(userId) {
try {
const user = await fetchUserPromise(userId);
console.log("User:", user);
const posts = await fetchPostsPromise(user.id);
console.log("Posts:", posts);
return { user, posts };
} catch (error) {
console.error("Error:", error);
}
}
// Sử dụng
getUserAndPosts(1);📊 So sánh 3 patterns:
| Pattern | Readability | Error Handling | Debugging |
|---|---|---|---|
| Callbacks | ⭐ | ⭐ | ⭐ |
| Promises | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| async/await | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
B. async/await Best Practices
// ========================================
// DEMO: async/await Patterns
// ========================================
// ✅ PATTERN 1: Try-catch cho error handling
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
return data;
} catch (error) {
console.error("Fetch failed:", error);
throw error; // Re-throw nếu cần handle ở level cao hơn
}
}
// ✅ PATTERN 2: Multiple await (sequential)
async function sequentialFetch() {
console.log("Fetching sequentially...");
const user = await fetchUserPromise(1); // Chờ 1s
const posts = await fetchPostsPromise(user.id); // Chờ thêm 1s
// Tổng: 2s
return { user, posts };
}
// ✅ PATTERN 3: Promise.all (parallel)
async function parallelFetch() {
console.log("Fetching in parallel...");
const [user, posts] = await Promise.all([
fetchUserPromise(1),
fetchPostsPromise(1),
]);
// Tổng: 1s (chạy đồng thời!)
return { user, posts };
}
// ❌ ANTI-PATTERN: Quên await
async function forgotAwait() {
const data = fetchUserPromise(1); // ❌ Quên await
console.log(data); // Promise {<pending>} - KHÔNG PHẢI DATA!
// ✅ ĐÚNG:
const correctData = await fetchUserPromise(1);
console.log(correctData); // { id: 1, name: 'John Doe' }
}
// ❌ ANTI-PATTERN: Không cần thiết dùng async
async function unnecessaryAsync() {
return 42; // ❌ Không có await nào → không cần async
}
// ✅ ĐÚNG:
function justReturnValue() {
return 42; // Đơn giản hơn
}⚠️ LƯU Ý QUAN TRỌNG:
Trong khóa học này, chúng ta CHỈ HỌC CONCEPTS của Promises/async/await.
Chúng ta sẽ THỰC SỰ SỬ DỤNG chúng từ Ngày 19 (Data Fetching).
Hôm nay tập trung vào HIỂU cách hoạt động!
Demo 2: ES6 Modules ⭐⭐
A. Export Patterns
// ========================================
// FILE: utils.js
// ========================================
// ✅ PATTERN 1: Named Exports (khuyến nghị cho utilities)
export const API_URL = "https://api.example.com";
export const MAX_RETRIES = 3;
export function formatDate(date) {
return date.toISOString().split("T")[0];
}
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// ✅ PATTERN 2: Export tập trung
const formatCurrency = (amount) => {
return `$${amount.toFixed(2)}`;
};
const formatPercentage = (value) => {
return `${(value * 100).toFixed(1)}%`;
};
export { formatCurrency, formatPercentage };
// ========================================
// FILE: User.js
// ========================================
// ✅ PATTERN 3: Default Export (cho main export)
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return `Hello, ${this.name}!`;
}
}
export default User;
// ⚠️ CHÚ Ý: Có thể combine default + named exports
export const USER_ROLES = {
ADMIN: "admin",
USER: "user",
GUEST: "guest",
};
// ========================================
// FILE: validators.js
// ========================================
// ✅ PATTERN 4: Re-export (aggregation pattern)
export { formatDate, formatCurrency } from "./utils.js";
export { default as User, USER_ROLES } from "./User.js";
// Giờ có thể import tất cả từ 1 file:
// import { formatDate, User, USER_ROLES } from './validators.js';B. Import Patterns
// ========================================
// FILE: app.js
// ========================================
// ✅ IMPORT 1: Named imports
import { formatDate, capitalize, API_URL } from "./utils.js";
console.log(formatDate(new Date())); // 2024-01-06
console.log(capitalize("hello")); // Hello
console.log(API_URL); // https://api.example.com
// ✅ IMPORT 2: Default import
import User from "./User.js"; // Tên có thể tùy chọn
const user = new User("John", "john@example.com");
console.log(user.greet()); // Hello, John!
// ✅ IMPORT 3: Rename imports
import { formatCurrency as formatMoney } from "./utils.js";
console.log(formatMoney(99.99)); // $99.99
// ✅ IMPORT 4: Import all as namespace
import * as Utils from "./utils.js";
console.log(Utils.API_URL);
console.log(Utils.formatDate(new Date()));
console.log(Utils.capitalize("world"));
// ✅ IMPORT 5: Combined import
import User, { USER_ROLES } from "./User.js";
const admin = new User("Admin", "admin@example.com");
console.log(admin.greet());
console.log(USER_ROLES.ADMIN); // 'admin'
// ❌ ANTI-PATTERN 1: Import default as named
// import { User } from './User.js'; // ❌ SAI! User là default export
// ❌ ANTI-PATTERN 2: Import named as default
// import formatDate from './utils.js'; // ❌ SAI! formatDate là named export
// ✅ ĐÚNG:
import User from "./User.js"; // Default import
import { formatDate } from "./utils.js"; // Named import📊 Decision Tree: Khi nào dùng gì?
┌─────────────────────────────────┐
│ Cần export từ file? │
└────────┬────────────────────────┘
│
├─ File có 1 "main thing"? → Default export
│ (VD: User class, Button component)
│
├─ File có nhiều utilities? → Named exports
│ (VD: formatDate, capitalize, sum...)
│
└─ Cả hai? → Default + Named exports
(VD: User class + USER_ROLES constant)Demo 3: Optional Chaining & Nullish Coalescing ⭐⭐⭐
A. Optional Chaining (?.)
// ========================================
// DEMO: Optional Chaining
// ========================================
// Setup: Nested object
const user = {
name: "John",
profile: {
bio: "Software Developer",
social: {
twitter: "@johndoe",
},
},
};
const userWithoutProfile = {
name: "Jane",
// Không có profile
};
// ❌ CÁCH SAI: Traditional null checks
function getTwitterOld(user) {
if (
user &&
user.profile &&
user.profile.social &&
user.profile.social.twitter
) {
return user.profile.social.twitter;
}
return null;
}
console.log(getTwitterOld(user)); // @johndoe
console.log(getTwitterOld(userWithoutProfile)); // null
// ✅ CÁCH ĐÚNG: Optional chaining
function getTwitterNew(user) {
return user?.profile?.social?.twitter ?? null;
}
console.log(getTwitterNew(user)); // @johndoe
console.log(getTwitterNew(userWithoutProfile)); // null
console.log(getTwitterNew(null)); // null (không crash!)
// 🎯 USE CASES
// USE CASE 1: Object property access
const address = user?.profile?.address?.city; // undefined (không crash)
// USE CASE 2: Array access
const users = [user, userWithoutProfile];
const firstUserTwitter = users[0]?.profile?.social?.twitter; // @johndoe
const thirdUserTwitter = users[2]?.profile?.social?.twitter; // undefined (không crash)
// USE CASE 3: Function calls
const onSave = user?.handleSave?.(); // Call nếu method tồn tại
const onSaveWithArgs = user?.handleSave?.(data); // Với arguments
// USE CASE 4: Dynamic property access
const fieldName = "twitter";
const socialHandle = user?.profile?.social?.[fieldName]; // @johndoe
// ⚠️ QUAN TRỌNG: Optional chaining chỉ check null/undefined
console.log(user?.name); // 'John'
console.log(user?.age); // undefined
console.log(null?.anything); // undefined
console.log(undefined?.anything); // undefined
// ❌ KHÔNG check falsy values khác
const emptyString = "";
console.log(emptyString?.length); // 0 (vẫn chạy vì '' không phải null/undefined)
const zero = 0;
console.log(zero?.toString()); // '0' (vẫn chạy vì 0 không phải null/undefined)B. Nullish Coalescing (??)
// ========================================
// DEMO: Nullish Coalescing
// ========================================
// ❌ VẤN ĐỀ với OR operator (||)
const settings = {
darkMode: false,
notifications: 0,
volume: "",
autoSave: null,
};
// ❌ SAI: OR operator
const darkMode1 = settings.darkMode || true;
console.log(darkMode1); // true (SAI! Mong muốn false)
const volume1 = settings.volume || 50;
console.log(volume1); // 50 (SAI! Mong muốn '')
const notifications1 = settings.notifications || 10;
console.log(notifications1); // 10 (SAI! Mong muốn 0)
// ✅ ĐÚNG: Nullish coalescing operator
const darkMode2 = settings.darkMode ?? true;
console.log(darkMode2); // false (ĐÚNG!)
const volume2 = settings.volume ?? 50;
console.log(volume2); // '' (ĐÚNG!)
const notifications2 = settings.notifications ?? 10;
console.log(notifications2); // 0 (ĐÚNG!)
const autoSave = settings.autoSave ?? true;
console.log(autoSave); // true (ĐÚNG! autoSave là null)
// 📊 SO SÁNH || vs ??
const testValues = [null, undefined, false, 0, "", "value"];
console.log("=== Comparing || vs ?? ===");
testValues.forEach((val) => {
const withOr = val || "default";
const withNullish = val ?? "default";
console.log(`Value: ${val}`);
console.log(` || : ${withOr}`);
console.log(` ?? : ${withNullish}`);
});
// Output:
// Value: null
// || : default
// ?? : default ✅ Cả hai đều dùng default
// Value: undefined
// || : default
// ?? : default ✅ Cả hai đều dùng default
// Value: false
// || : default ❌ Sai! false là valid value
// ?? : false ✅ Giữ nguyên false
// Value: 0
// || : default ❌ Sai! 0 là valid number
// ?? : 0 ✅ Giữ nguyên 0
// Value: ''
// || : default ❌ Sai! '' là valid string
// ?? : ✅ Giữ nguyên ''
// Value: value
// || : value
// ?? : value ✅ Cả hai đều giữ nguyên
// 🎯 COMBINING ?. và ??
const getUsername = (user) => {
return user?.profile?.username ?? "Anonymous";
};
console.log(getUsername(user)); // 'John' (nếu có username)
console.log(getUsername(userWithoutProfile)); // 'Anonymous'
console.log(getUsername(null)); // 'Anonymous'C. Real-world Examples
// ========================================
// DEMO: Real-world Use Cases
// ========================================
// USE CASE 1: API Response handling
function processUserData(apiResponse) {
// ✅ Safe access với optional chaining
const userName = apiResponse?.data?.user?.name ?? "Unknown User";
const email = apiResponse?.data?.user?.email ?? "no-email@example.com";
const age = apiResponse?.data?.user?.age ?? 0; // 0 là default hợp lý
// ✅ Safe method calls
const welcomeMessage = apiResponse?.data?.getMessage?.() ?? "Welcome!";
return {
userName,
email,
age,
welcomeMessage,
};
}
// Test với different responses
console.log(
processUserData({
data: {
user: {
name: "John",
email: "john@example.com",
age: 25,
},
},
}),
);
// { userName: 'John', email: 'john@example.com', age: 25, welcomeMessage: 'Welcome!' }
console.log(processUserData({}));
// { userName: 'Unknown User', email: 'no-email@example.com', age: 0, welcomeMessage: 'Welcome!' }
console.log(processUserData(null));
// { userName: 'Unknown User', email: 'no-email@example.com', age: 0, welcomeMessage: 'Welcome!' }
// USE CASE 2: Configuration objects
function initializeApp(config) {
const settings = {
theme: config?.theme ?? "light",
language: config?.language ?? "en",
debug: config?.debug ?? false, // false là valid value!
maxRetries: config?.maxRetries ?? 3,
timeout: config?.timeout ?? 5000,
};
return settings;
}
console.log(initializeApp({ theme: "dark", debug: false }));
// { theme: 'dark', language: 'en', debug: false, maxRetries: 3, timeout: 5000 }
console.log(initializeApp(null));
// { theme: 'light', language: 'en', debug: false, maxRetries: 3, timeout: 5000 }
// USE CASE 3: Form data với potential nulls
function getFormData(formState) {
return {
firstName: formState?.firstName?.trim() ?? "",
lastName: formState?.lastName?.trim() ?? "",
age: formState?.age ?? null, // null có ý nghĩa: chưa nhập
acceptTerms: formState?.acceptTerms ?? false, // false: chưa chấp nhận
};
}Demo 4: Array Methods Nâng Cao ⭐⭐⭐
// ========================================
// DEMO: Advanced Array Methods
// ========================================
// Sample data
const products = [
{
id: 1,
name: "Laptop",
price: 1000,
inStock: true,
category: "Electronics",
},
{ id: 2, name: "Mouse", price: 25, inStock: true, category: "Electronics" },
{
id: 3,
name: "Keyboard",
price: 75,
inStock: false,
category: "Electronics",
},
{
id: 4,
name: "Monitor",
price: 300,
inStock: true,
category: "Electronics",
},
{ id: 5, name: "Desk", price: 200, inStock: true, category: "Furniture" },
];
// ========================================
// METHOD 1: find() - Tìm element đầu tiên thỏa điều kiện
// ========================================
// ✅ USE CASE: Tìm product theo ID
const findProductById = (id) => {
return products.find((product) => product.id === id);
};
console.log(findProductById(3));
// { id: 3, name: 'Keyboard', price: 75, inStock: false, category: 'Electronics' }
console.log(findProductById(999));
// undefined (không tìm thấy)
// ✅ USE CASE: Tìm product out of stock
const outOfStockProduct = products.find((p) => !p.inStock);
console.log(outOfStockProduct);
// { id: 3, name: 'Keyboard', ... }
// ❌ ANTI-PATTERN: Dùng filter rồi lấy [0]
const wrong = products.filter((p) => p.id === 3)[0]; // ❌ Không hiệu quả
// filter() duyệt HẾT array, find() dừng khi tìm thấy!
// ========================================
// METHOD 2: findIndex() - Tìm index của element
// ========================================
// ✅ USE CASE: Tìm vị trí để update/delete
const keyboardIndex = products.findIndex((p) => p.name === "Keyboard");
console.log(keyboardIndex); // 2
// ✅ USE CASE: Check nếu không tồn tại
const notFoundIndex = products.findIndex((p) => p.name === "Chair");
console.log(notFoundIndex); // -1 (không tìm thấy)
if (notFoundIndex === -1) {
console.log("Product not found");
}
// ========================================
// METHOD 3: some() - Check NẾU CÓ ÍT NHẤT 1
// ========================================
// ✅ USE CASE: Check có sản phẩm hết hàng không?
const hasOutOfStock = products.some((p) => !p.inStock);
console.log(hasOutOfStock); // true
// ✅ USE CASE: Check có sản phẩm đắt không?
const hasExpensiveProduct = products.some((p) => p.price > 500);
console.log(hasExpensiveProduct); // true (Laptop = 1000)
// ✅ USE CASE: Validation
const allPricesValid = products.some((p) => p.price < 0);
console.log(allPricesValid); // false (không có giá âm)
// ❌ ANTI-PATTERN: Dùng filter().length > 0
const wrong2 = products.filter((p) => p.price > 500).length > 0; // ❌
// filter() duyệt HẾT array, some() dừng khi tìm thấy!
// ========================================
// METHOD 4: every() - Check TẤT CẢ
// ========================================
// ✅ USE CASE: Check TẤT CẢ có sẵn hàng?
const allInStock = products.every((p) => p.inStock);
console.log(allInStock); // false (có 1 cái out of stock)
// ✅ USE CASE: Validation - TẤT CẢ có giá hợp lệ?
const allPricesPositive = products.every((p) => p.price > 0);
console.log(allPricesPositive); // true
// ✅ USE CASE: Check TẤT CẢ thuộc category
const allElectronics = products.every((p) => p.category === "Electronics");
console.log(allElectronics); // false (có Desk là Furniture)
// 📊 SO SÁNH some() vs every()
console.log("\n=== some() vs every() ===");
const numbers = [2, 4, 6, 7, 8];
console.log(
"Có số chẵn?",
numbers.some((n) => n % 2 === 0),
); // true
console.log(
"TẤT CẢ chẵn?",
numbers.every((n) => n % 2 === 0),
); // false
// ========================================
// METHOD 5: Object.keys() / values() / entries()
// ========================================
const user = {
name: "John",
age: 30,
email: "john@example.com",
role: "admin",
};
// ✅ Object.keys() - Lấy mảng keys
const keys = Object.keys(user);
console.log(keys);
// ['name', 'age', 'email', 'role']
// ✅ USE CASE: Loop qua keys
keys.forEach((key) => {
console.log(`${key}: ${user[key]}`);
});
// ✅ Object.values() - Lấy mảng values
const values = Object.values(user);
console.log(values);
// ['John', 30, 'john@example.com', 'admin']
// ✅ USE CASE: Check nếu có value
const hasAdmin = values.includes("admin");
console.log(hasAdmin); // true
// ✅ Object.entries() - Lấy mảng [key, value] pairs
const entries = Object.entries(user);
console.log(entries);
// [
// ['name', 'John'],
// ['age', 30],
// ['email', 'john@example.com'],
// ['role', 'admin']
// ]
// ✅ USE CASE: Loop với destructuring
entries.forEach(([key, value]) => {
console.log(`${key}: ${value}`);
});
// ✅ USE CASE: Filter object properties
const filteredEntries = entries.filter(([key, value]) => {
return typeof value === "string";
});
const stringFieldsOnly = Object.fromEntries(filteredEntries);
console.log(stringFieldsOnly);
// { name: 'John', email: 'john@example.com', role: 'admin' }
// ✅ USE CASE: Transform object
const uppercasedUser = Object.fromEntries(
entries.map(([key, value]) => {
return [key, typeof value === "string" ? value.toUpperCase() : value];
}),
);
console.log(uppercasedUser);
// { name: 'JOHN', age: 30, email: 'JOHN@EXAMPLE.COM', role: 'ADMIN' }
// ========================================
// COMBINING METHODS - Real-world Example
// ========================================
// ✅ SCENARIO: Product search & filter
function searchProducts(query, filters = {}) {
let results = products;
// Filter by search query
if (query) {
results = results.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase()),
);
}
// Filter by inStock
if (filters.inStock !== undefined) {
results = results.filter((p) => p.inStock === filters.inStock);
}
// Filter by price range
if (filters.minPrice !== undefined) {
results = results.filter((p) => p.price >= filters.minPrice);
}
if (filters.maxPrice !== undefined) {
results = results.filter((p) => p.price <= filters.maxPrice);
}
// Filter by category
if (filters.category) {
results = results.filter((p) => p.category === filters.category);
}
return results;
}
// Test
console.log(searchProducts("", { inStock: true, minPrice: 50 }));
// [Laptop, Keyboard, Monitor, Desk] - sản phẩm có sẵn, giá >= 50
console.log(searchProducts("key", {}));
// [Keyboard]
// ✅ SCENARIO: Cart calculations
const cart = [
{ productId: 1, quantity: 2 }, // Laptop x 2
{ productId: 2, quantity: 1 }, // Mouse x 1
];
function calculateCartTotal(cart) {
return cart.reduce((total, item) => {
const product = products.find((p) => p.id === item.productId);
return total + (product ? product.price * item.quantity : 0);
}, 0);
}
console.log(calculateCartTotal(cart));
// 2025 (1000*2 + 25*1)
// ✅ SCENARIO: Check nếu cart hợp lệ
function isCartValid(cart) {
return cart.every((item) => {
const product = products.find((p) => p.id === item.productId);
return product && product.inStock && item.quantity > 0;
});
}
console.log(isCartValid(cart)); // true hoặc false📊 Method Selection Matrix:
| Method | Returns | Stops Early? | Use when |
|---|---|---|---|
find() | Element hoặc undefined | ✅ Yes | Tìm 1 element |
findIndex() | Index hoặc -1 | ✅ Yes | Tìm vị trí element |
some() | Boolean | ✅ Yes | Check ÍT NHẤT 1 thỏa điều kiện |
every() | Boolean | ✅ Yes | Check TẤT CẢ thỏa điều kiện |
filter() | Array | ❌ No | Lấy TẤT CẢ elements thỏa điều kiện |
map() | Array | ❌ No | Transform mọi element |
🔨 PHẦN 3: BÀI TẬP THỰC HÀNH (60 phút)
⭐ Exercise 1: Promises Basic Practice (15 phút)
/**
* 🎯 Mục tiêu: Hiểu cách tạo và sử dụng Promise cơ bản
* ⏱️ Thời gian: 15 phút
* 🚫 KHÔNG dùng: async/await (chưa thực hành sâu), fetch API, external libraries
*
* Requirements:
* 1. Tạo Promise simulates API call
* 2. Handle success case
* 3. Handle error case
* 4. Chain multiple promises
*
* 💡 Gợi ý: Dùng setTimeout để simulate async operation
*/
// ❌ CÁCH SAI: Không handle errors
function fetchDataWrong(id) {
return new Promise((resolve) => {
// ❌ Thiếu reject!
setTimeout(() => {
resolve({ id, name: "John" });
}, 1000);
});
}
// Problem: Không thể handle errors!
fetchDataWrong(1).then((data) => console.log(data));
// Nếu có lỗi xảy ra → không bắt được!
// ✅ CÁCH ĐÚNG: Handle cả success và error
function fetchDataCorrect(id) {
return new Promise((resolve, reject) => {
// ✅ Có reject
setTimeout(() => {
if (id > 0) {
resolve({ id, name: "John" });
} else {
reject(new Error("Invalid ID")); // ✅ Reject với meaningful error
}
}, 1000);
});
}
// Sử dụng đúng:
fetchDataCorrect(1)
.then((data) => {
console.log("Success:", data);
return data.id; // ✅ Return value cho promise tiếp theo
})
.then((id) => {
console.log("ID:", id);
})
.catch((error) => {
console.error("Error:", error.message);
});
// 🎯 NHIỆM VỤ CỦA BẠN:
// TODO 1: Tạo function fetchUser trả về Promise
// - Input: userId (number)
// - Nếu userId > 0: resolve với { id: userId, name: 'User ' + userId }
// - Nếu userId <= 0: reject với Error('Invalid user ID')
// - Delay: 1 second
function fetchUser(userId) {
// YOUR CODE HERE
}
// TODO 2: Tạo function fetchPosts trả về Promise
// - Input: userId (number)
// - Resolve với array of posts: [{ id: 1, title: 'Post 1', userId }, { id: 2, title: 'Post 2', userId }]
// - Delay: 1 second
function fetchPosts(userId) {
// YOUR CODE HERE
}
// TODO 3: Chain 2 promises trên
// - Fetch user với ID = 1
// - Sau đó fetch posts của user đó
// - Log ra console: user và posts
// - Handle errors
// YOUR CODE HERE
// 📝 Test cases:
// fetchUser(1) → should resolve
// fetchUser(0) → should reject
// fetchUser(1).then(() => fetchPosts(1)) → should resolve
/**
* ✅ SOLUTION: Promises Basic Practice
*/
// TODO 1: Tạo function fetchUser
function fetchUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({
id: userId,
name: `User ${userId}`,
});
} else {
reject(new Error("Invalid user ID"));
}
}, 1000);
});
}
// TODO 2: Tạo function fetchPosts
function fetchPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: 1, title: "Post 1", userId },
{ id: 2, title: "Post 2", userId },
]);
}, 1000);
});
}
// TODO 3: Chain promises
console.log("Fetching user and posts...");
fetchUser(1)
.then((user) => {
console.log("✅ User fetched:", user);
return fetchPosts(user.id); // Chain next promise
})
.then((posts) => {
console.log("✅ Posts fetched:", posts);
})
.catch((error) => {
console.error("❌ Error:", error.message);
});
// Test error case
fetchUser(0)
.then((user) => console.log(user))
.catch((error) => console.error("❌ Expected error:", error.message));
/**
* 🎓 KEY LEARNINGS:
* 1. Promise nhận 2 params: resolve, reject
* 2. Luôn handle cả success (then) và error (catch)
* 3. Return value trong then để chain promises
* 4. Reject với Error object (có message)
*/⭐⭐ Exercise 2: ES6 Modules Practice (25 phút)
/**
* 🎯 Mục tiêu: Thực hành import/export patterns
* ⏱️ Thời gian: 25 phút
*
* Scenario: Bạn đang xây dựng utility library cho React app
*
* 🤔 PHÂN TÍCH:
* Approach A: Tất cả trong 1 file utils.js
* Pros: Đơn giản, dễ tìm
* Cons: File quá lớn, khó maintain
*
* Approach B: Split thành nhiều files, re-export từ index
* Pros: Organized, easy to maintain
* Cons: Phức tạp hơn một chút
*
* 💭 CHỌN APPROACH B - Better for large projects
*/
// ========================================
// FILE STRUCTURE:
// src/
// utils/
// string.js
// number.js
// date.js
// index.js (re-exports)
// app.js
// ========================================
// TODO 1: Tạo file string.js với các functions:
// - capitalize(str): Viết hoa chữ cái đầu
// - slugify(str): 'Hello World' -> 'hello-world'
// - truncate(str, length): Cắt string và thêm '...'
// FILE: utils/string.js
// YOUR CODE HERE
// Hints:
// - Export named cho multiple utilities
// - Pure functions (no side effects)
// - Handle edge cases (null, undefined, empty string)
// TODO 2: Tạo file number.js với các functions:
// - formatCurrency(amount): 1234.5 -> '$1,234.50'
// - formatPercentage(value): 0.156 -> '15.6%'
// - clamp(value, min, max): Giới hạn value trong range
// FILE: utils/number.js
// YOUR CODE HERE
// TODO 3: Tạo file date.js với các functions:
// - formatDate(date): Date -> 'YYYY-MM-DD'
// - getRelativeTime(date): 'ago 2 hours ago'
// - isToday(date): Check nếu là hôm nay
// FILE: utils/date.js
// YOUR CODE HERE
// TODO 4: Tạo file index.js re-export tất cả
// FILE: utils/index.js
// YOUR CODE HERE
// Hint: export { ... } from './string.js'
// TODO 5: Sử dụng trong app.js
// FILE: app.js
// Import tất cả và sử dụng
// YOUR CODE HERE
// 📝 Test cases:
const testString = " Hello World ";
const testNumber = 1234.567;
const testDate = new Date();
// Expected usage:
// capitalize(testString) -> 'Hello world'
// formatCurrency(testNumber) -> '$1,234.57'
// formatDate(testDate) -> '2024-01-06'💡 Solution
📁 src/utils/string.js
// utils/string.js
export function capitalize(str) {
if (typeof str !== "string") return "";
const trimmed = str.trim();
if (!trimmed) return "";
return trimmed.charAt(0).toUpperCase() + trimmed.slice(1).toLowerCase();
}
export function slugify(str) {
if (typeof str !== "string") return "";
return str
.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, "")
.replace(/\s+/g, "-")
.replace(/-+/g, "-");
}
export function truncate(str, length) {
if (typeof str !== "string") return "";
if (typeof length !== "number" || length <= 0) return "";
if (str.length <= length) return str;
return str.slice(0, length) + "...";
}📁 src/utils/number.js
// utils/number.js
export function formatCurrency(amount) {
if (typeof amount !== "number" || isNaN(amount)) return "$0.00";
return amount.toLocaleString("en-US", {
style: "currency",
currency: "USD",
});
}
export function formatPercentage(value) {
if (typeof value !== "number" || isNaN(value)) return "0%";
return `${(value * 100).toFixed(1)}%`;
}
export function clamp(value, min, max) {
if ([value, min, max].some((v) => typeof v !== "number")) return min;
return Math.min(Math.max(value, min), max);
}📁 src/utils/date.js
// utils/date.js
export function formatDate(date) {
if (!(date instanceof Date) || isNaN(date)) return "";
return date.toISOString().split("T")[0];
}
export function isToday(date) {
if (!(date instanceof Date)) return false;
const today = new Date();
return (
date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear()
);
}
export function getRelativeTime(date) {
if (!(date instanceof Date)) return "";
const diffMs = Date.now() - date.getTime();
const diffSeconds = Math.floor(diffMs / 1000);
const diffMinutes = Math.floor(diffSeconds / 60);
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffSeconds < 60) return "just now";
if (diffMinutes < 60) return `${diffMinutes} minutes ago`;
if (diffHours < 24) return `${diffHours} hours ago`;
return `${diffDays} days ago`;
}📁 src/utils/index.js (re-export)
// utils/index.js
export * from "./string.js";
export * from "./number.js";
export * from "./date.js";📁 src/app.js (sử dụng)
// app.js
import {
capitalize,
slugify,
truncate,
formatCurrency,
formatPercentage,
clamp,
formatDate,
getRelativeTime,
isToday,
} from "./utils/index.js";
// Test cases
const testString = " Hello World ";
const testNumber = 1234.567;
const testDate = new Date();
console.log(capitalize(testString)); // 'Hello world'
console.log(slugify(testString)); // 'hello-world'
console.log(truncate(testString, 5)); // ' Hell...'
console.log(formatCurrency(testNumber)); // '$1,234.57'
console.log(formatPercentage(0.156)); // '15.6%'
console.log(clamp(15, 0, 10)); // 10
console.log(formatDate(testDate)); // 'YYYY-MM-DD'
console.log(getRelativeTime(testDate)); // 'just now'
console.log(isToday(testDate)); // true✅ Tổng kết best practices đã áp dụng
- ✔️ Named exports → tree-shaking tốt cho React
- ✔️ Pure functions
- ✔️ Centralized re-export (
index.js) - ✔️ Edge cases handled
- ✔️ Scalable cho project lớn
⭐⭐⭐ Exercise 3: Optional Chaining & Real-world Scenario (40 phút)
/**
* 🎯 Mục tiêu: Xử lý nested data an toàn với ?. và ??
* ⏱️ Thời gian: 40 phút
*
* 📋 Product Requirements:
* User Story: "Là developer, tôi muốn xử lý API response an toàn,
* tránh crashes khi data structure thay đổi"
*
* ✅ Acceptance Criteria:
* - [ ] Không crash khi nested property không tồn tại
* - [ ] Có default values hợp lý
* - [ ] Giữ được falsy values hợp lệ (0, false, '')
* - [ ] Code clean, dễ đọc
*
* 🎨 Technical Constraints:
* - KHÔNG dùng try-catch để handle missing properties
* - PHẢI dùng ?. và ?? operators
*
* 🚨 Edge Cases cần handle:
* - API trả về null/undefined
* - Nested property không tồn tại
* - Array có thể empty
* - Method có thể không tồn tại
*/
// ========================================
// SAMPLE API RESPONSES
// ========================================
const fullResponse = {
data: {
user: {
id: 1,
name: "John Doe",
email: "john@example.com",
profile: {
avatar: "https://avatar.com/john.jpg",
bio: "Software Developer",
location: {
city: "San Francisco",
country: "USA",
},
social: {
twitter: "@johndoe",
github: "johndoe",
},
},
preferences: {
theme: "dark",
notifications: {
email: true,
push: false,
},
},
stats: {
posts: 42,
followers: 150,
following: 75,
},
},
posts: [
{ id: 1, title: "First Post", likes: 10 },
{ id: 2, title: "Second Post", likes: 0 },
],
},
meta: {
timestamp: Date.now(),
version: "1.0",
},
};
const partialResponse = {
data: {
user: {
id: 2,
name: "Jane Smith",
email: "jane@example.com",
// Không có profile, preferences, stats
},
// Không có posts
},
};
const errorResponse = null;
const emptyResponse = {};
// ========================================
// TODO 1: Tạo function processUserData
// ========================================
/**
* Process API response và extract user data an toàn
* @param {Object} response - API response
* @returns {Object} Processed user data với defaults
*/
function processUserData(response) {
// YOUR CODE HERE
// Requirements:
// 1. Extract user info với defaults
// 2. Handle missing nested properties
// 3. Keep falsy values như 0, false
// 4. Return structure:
return {
id: null,
name: "Anonymous",
email: "no-email",
avatar: "/default-avatar.png",
bio: "",
location: "Unknown",
social: {
twitter: null,
github: null,
},
theme: "light",
emailNotifications: true,
pushNotifications: false,
stats: {
posts: 0,
followers: 0,
following: 0,
},
};
}
/**
* ✅ SOLUTION
*/
function processUserData(response) {
const user = response?.data?.user;
const profile = user?.profile;
const preferences = user?.preferences;
const stats = user?.stats ?? {};
const postsLength = response?.data?.posts?.length ?? 0;
return {
id: user?.id ?? null,
name: user?.name ?? "Anonymous",
email: user?.email ?? "no-email",
avatar: profile?.avatar ?? "/default-avatar.png",
bio: profile?.bio ?? "",
location:
profile?.location?.city && profile?.location?.country
? `${profile?.location?.city}, ${profile?.location?.country}`
: "Unknown",
social: {
twitter: profile?.social?.twitter ?? null,
github: profile?.social?.github ?? null,
},
theme: preferences?.theme ?? "light",
emailNotifications: preferences?.notifications?.email ?? true,
pushNotifications: preferences?.notifications?.push ?? false,
stats: {
posts: postsLength,
followers: stats.followers ?? 0,
following: stats.following ?? 0,
},
};
}
// ========================================
// TODO 2: Tạo function getPostsSummary
// ========================================
/**
* Get summary of user's posts
* @param {Object} response - API response
* @returns {Object} Posts summary
*/
function getPostsSummary(response) {
// YOUR CODE HERE
// Requirements:
// 1. Handle missing posts array
// 2. Calculate total posts
// 3. Calculate total likes
// 4. Find most liked post
// 5. Return structure:
return {
totalPosts: 0,
totalLikes: 0,
averageLikes: 0,
mostLikedPost: null,
};
}
/**
* ✅ SOLUTION
*/
function getPostsSummary(response) {
const posts = response?.data?.posts ?? [];
// Tính toán một lần duy nhất
const totalPosts = posts.length;
const totalLikes = posts.reduce((sum, post) => sum + (post?.likes ?? 0), 0);
// Tìm bài post có lượt like cao nhất (nếu có nhiều bài cùng số like → lấy bài đầu tiên)
const mostLikedPost =
totalPosts > 0
? posts.reduce((max, post) =>
(post?.likes ?? 0) > (max?.likes ?? 0) ? post : max,
)
: null;
const averageLikes =
totalPosts > 0 ? Number((totalLikes / totalPosts).toFixed(1)) : 0;
return {
totalPosts,
totalLikes,
averageLikes,
mostLikedPost, // giữ nguyên object gốc hoặc null
};
}
// ========================================
// TODO 3: Tạo function getUserDisplayName
// ========================================
/**
* Get user display name với fallback logic
* Priority: name > email username > 'User #{id}' > 'Guest'
* @param {Object} response - API response
* @returns {string} Display name
*/
function getUserDisplayName(response) {
// YOUR CODE HERE
// Hints:
// - Check name first
// - Extract username from email (before @)
// - Use id nếu có
// - Final fallback: 'Guest'
}
/**
* ✅ SOLUTION
*/
function getUserDisplayName(response) {
const user = response?.data?.user;
// Priority 1: name (nếu tồn tại và không phải chuỗi rỗng)
if (user?.name?.trim()) {
return user.name.trim();
}
// Priority 2: username từ email (phần trước @)
const emailUsername = user?.email?.split("@")[0]?.trim();
if (emailUsername) {
return emailUsername;
}
// Priority 3: User # + id
if (user?.id != null) {
return `User #${user.id}`;
}
// Final fallback
return "Guest";
}
// ========================================
// TODO 4: Tạo function canUserPerformAction
// ========================================
/**
* Check nếu user có thể thực hiện action dựa trên permissions
* @param {Object} response - API response
* @param {string} action - Action name
* @returns {boolean}
*/
function canUserPerformAction(response, action) {
// YOUR CODE HERE
// Giả định cấu trúc :
// response.data.user.permissions = {
// canPost: true,
// canDelete: false,
// canEdit: true
// }
// Yêu cầu:
// - Xử lý đối tượng permissions bị thiếu
// - Xử lý permissions cụ thể bị thiếu
// - Mặc định: false (từ chối theo mặc định)
}
/**
* ✅ SOLUTION
*/
function canUserPerformAction(response, action) {
// Kiểm tra response và action hợp lệ
if (!response || typeof action !== "string" || !action.trim()) {
return false;
}
const permissions = response?.data?.user?.permissions ?? {};
// Trả về giá trị permission nếu tồn tại, ngược lại false
return permissions[action] === true;
}
// 📝 TEST CASES
console.log("=== Test processUserData ===");
console.log(processUserData(fullResponse));
// Should have all data
console.log(processUserData(partialResponse));
// Should have defaults for missing fields
console.log(processUserData(errorResponse));
// Should return all defaults, no crash
console.log(processUserData(emptyResponse));
// Should return all defaults, no crash
console.log("\n=== Test getPostsSummary ===");
console.log(getPostsSummary(fullResponse));
// { totalPosts: 2, totalLikes: 10, ... }
console.log(getPostsSummary(partialResponse));
// { totalPosts: 0, totalLikes: 0, ... }
console.log("\n=== Test getUserDisplayName ===");
console.log(getUserDisplayName(fullResponse)); // 'John Doe'
console.log(getUserDisplayName(partialResponse)); // 'Jane Smith'
console.log(getUserDisplayName(errorResponse)); // 'Guest'
console.log(
getUserDisplayName({
data: { user: { id: 5 } },
}),
); // 'User #5'
console.log("\n=== Test canUserPerformAction ===");
console.log(canUserPerformAction(fullResponse, "canPost")); // depends on data
console.log(canUserPerformAction(partialResponse, "canPost")); // false
console.log(canUserPerformAction(errorResponse, "canPost")); // false⭐⭐⭐⭐ Exercise 4: Architecture Decision - Array Methods (60 phút)
/**
* 🎯 Mục tiêu: Chọn đúng array method cho từng use case
* ⏱️ Thời gian: 60 phút
*
* 🏗️ PHASE 1: Research & Design (20 phút)
*
* Scenario: Bạn đang xây dựng Product Filter System cho e-commerce
*
* Features cần implement:
* 1. Search products by name
* 2. Filter by price range
* 3. Filter by availability
* 4. Sort by different criteria
* 5. Check inventory status
* 6. Calculate statistics
*
*
* ## Context
* Cần xử lý large product list (100-1000 items) với multiple filters.
* Performance quan trọng - avoid unnecessary iterations.
*
* ## Decision Points
*
* 1. Nên dùng method nào để search?
* - Option A: filter() + [0]
* - Option B: find()
* - Option C: for loop
*
* 2. Check inventory - any out of stock?
* - Option A: filter().length > 0
* - Option B: some()
* - Option C: for loop with early return
*
* 3. Validate all products - có giá hợp lệ?
* - Option A: filter().length === array.length
* - Option B: every()
* - Option C: !some() with inverted condition
*
* TODO: Phân tích từng option, đưa ra decision và rationale
*/💡 Solution Phase 1
/**
*
* =====================================================
* BỐI CẢNH (CONTEXT)
* =====================================================
*
* - Xử lý danh sách sản phẩm lớn (100–1000+ items),
* thường kết hợp nhiều filter (lọc) / search (tìm kiếm) / sort (sắp xếp).
*
* - Hiệu năng (performance) là yếu tố quan trọng:
* → tránh lặp thừa (unnecessary iterations)
* → giảm cấp phát bộ nhớ (memory allocation) khi không cần thiết.
*
* - Ưu tiên code khai báo (declarative):
* → dễ đọc (readable)
* → dễ bảo trì (maintainable)
* nhưng không đánh đổi hiệu năng đáng kể trong quy mô này.
*
* - Các JS engine hiện đại (V8, SpiderMonkey, JavaScriptCore)
* đã tối ưu rất tốt cho array methods (các hàm xử lý mảng).
* → khác biệt hiệu năng chỉ rõ rệt khi array > 10k–100k phần tử
* hoặc nằm trong hot path (đoạn code chạy rất thường xuyên).
*
* =====================================================
* QUYẾT ĐỊNH 1: TÌM SẢN PHẨM THEO TÊN
* (Chỉ cần MỘT sản phẩm hoặc danh sách khớp)
* =====================================================
*
* OPTION A: filter()[0]
* - Ưu điểm:
* + Dễ viết nếu sau này cần nhiều kết quả
* - Nhược điểm:
* - Luôn duyệt TOÀN BỘ mảng dù chỉ cần 1 kết quả
* - Tốn CPU nếu tìm thấy sớm
*
* OPTION B: find()
* - Ưu điểm:
* + Thoát sớm (early exit) khi tìm thấy phần tử đầu tiên
* + Phù hợp nhất cho bài toán "tìm một sản phẩm"
* - Nhược điểm:
* - Chỉ trả về 1 phần tử (cần nhiều → dùng filter)
*
* OPTION C: for loop
* - Ưu điểm:
* + Có thể tối ưu nhất về mặt lý thuyết
* (return sớm, không tốn chi phí gọi hàm
* – no function call overhead)
* - Nhược điểm:
* - Code mệnh lệnh (imperative)
* - Dài dòng, khó nối (chain) với filter khác
* - Khó đọc, khó bảo trì
*
* 👉 CHỌN: OPTION B – find()
*
* LÝ DO:
* - Tìm theo tên thường chỉ cần MỘT sản phẩm cụ thể
* - find() có thoát sớm → nhanh hơn filter() khi match nằm đầu/giữa mảng
* - Với 100–1000 phần tử, đây là best practice (thực hành tốt nhất)
* - Nếu cần TẤT CẢ kết quả khớp → dùng filter()
*
* =====================================================
* QUYẾT ĐỊNH 2: KIỂM TRA TỒN KHO
* (Có sản phẩm nào hết hàng không?)
* =====================================================
*
* OPTION A: filter().length > 0
* - Ưu điểm:
* + Dễ hiểu
* - Nhược điểm:
* - Duyệt toàn bộ mảng
* - Tạo mảng mới không cần thiết (tốn bộ nhớ)
*
* OPTION B: some()
* - Ưu điểm:
* + Thoát sớm khi tìm thấy MỘT phần tử thỏa mãn
* + Tối ưu cho câu hỏi "có ... không?"
* - Nhược điểm:
* - Không có trong trường hợp này
*
* OPTION C: for loop + return sớm
* - Ưu điểm:
* + Có thể nhanh nhất về mặt lý thuyết
* - Nhược điểm:
* - Imperative, khó đọc, khó kết hợp logic khác
*
* 👉 CHỌN: OPTION B – some()
*
* LÝ DO:
* - some() được thiết kế đúng cho câu hỏi
* "có ít nhất một phần tử thỏa mãn không?"
* - Thoát sớm → hiệu năng rất tốt nếu match nằm đầu danh sách
* - Code rõ ràng, dễ đọc:
*
* products.some(p => p.stock === 0)
*
* =====================================================
* QUYẾT ĐỊNH 3: KIỂM TRA TÍNH HỢP LỆ CỦA GIÁ
* (Tất cả sản phẩm có giá hợp lệ không?)
* =====================================================
*
* OPTION A: filter().length === array.length
* - Ưu điểm:
* + Dễ hiểu nếu quen filter
* - Nhược điểm:
* - Luôn duyệt toàn bộ mảng
* - Tạo mảng mới → kém hiệu quả
*
* OPTION B: every()
* - Ưu điểm:
* + Thoát sớm khi gặp MỘT phần tử không hợp lệ
* + Tối ưu cho validation (kiểm tra hợp lệ)
* - Nhược điểm:
* - Không có
*
* OPTION C: !some() với điều kiện ngược
* - Ưu điểm:
* + Logic & hiệu năng tương đương every()
* - Nhược điểm:
* - Logic "ngược", dễ gây nhầm khi đọc
*
* 👉 CHỌN: OPTION B – every()
*
* LÝ DO:
* - every() đúng ngữ nghĩa cho câu hỏi
* "tất cả phần tử có thỏa mãn không?"
* - Thoát sớm → không duyệt thừa
* - Code rõ ràng:
*
* products.every(
* p => p.price > 0 && Number.isFinite(p.price)
* )
*
* =====================================================
* KHUYẾN NGHỊ CHUNG (RECOMMENDATIONS)
* =====================================================
*
* - Khi kết hợp nhiều filter (lọc):
* → ưu tiên chaining (nối các bước xử lý):
*
* const filtered = products
* .filter(p =>
* p.name.toLowerCase().includes(query.toLowerCase())
* )
* .filter(p => p.price >= min && p.price <= max)
* .filter(p => p.stock > 0)
* .sort((a, b) => b.rating - a.rating);
*
*/// ========================================
// 💻 PHASE 2: Implementation (30 phút)
// ========================================
// Sample data
const products = [
{
id: 1,
name: "Laptop Pro",
price: 1299,
inStock: true,
category: "Electronics",
rating: 4.5,
},
{
id: 2,
name: "Wireless Mouse",
price: 29,
inStock: true,
category: "Electronics",
rating: 4.2,
},
{
id: 3,
name: "Mechanical Keyboard",
price: 89,
inStock: false,
category: "Electronics",
rating: 4.7,
},
{
id: 4,
name: "USB-C Cable",
price: 15,
inStock: true,
category: "Accessories",
rating: 4.0,
},
{
id: 5,
name: "Monitor Stand",
price: 45,
inStock: true,
category: "Accessories",
rating: 4.3,
},
{
id: 6,
name: "4K Monitor",
price: 399,
inStock: false,
category: "Electronics",
rating: 4.8,
},
{
id: 7,
name: "Desk Lamp",
price: 35,
inStock: true,
category: "Furniture",
rating: 4.1,
},
{
id: 8,
name: "Webcam HD",
price: 79,
inStock: true,
category: "Electronics",
rating: 4.4,
},
];
// TODO 1: Implement ProductSearch class
class ProductSearch {
constructor(products) {
this.products = products;
}
// Find product by ID (use appropriate method)
findById(id) {
// YOUR CODE HERE
}
// Search products by name (partial match)
searchByName(query) {
// YOUR CODE HERE
}
// Filter by price range
filterByPriceRange(min, max) {
// YOUR CODE HERE
}
// Filter by availability
filterByAvailability(inStock) {
// YOUR CODE HERE
}
// Filter by category
filterByCategory(category) {
// YOUR CODE HERE
}
// Complex filter - combine multiple criteria
advancedSearch(criteria) {
// criteria = { query, minPrice, maxPrice, inStock, category, minRating }
// YOUR CODE HERE
// Hint: Chain filters carefully
}
}
// TODO 2: Implement InventoryManager class
class InventoryManager {
constructor(products) {
this.products = products;
}
// Check if ANY product is out of stock (use appropriate method)
hasOutOfStock() {
// YOUR CODE HERE
}
// Check if ALL products are in stock (use appropriate method)
allInStock() {
// YOUR CODE HERE
}
// Check if ANY product in specific category is out of stock
categoryHasOutOfStock(category) {
// YOUR CODE HERE
}
// Get first out of stock product (use appropriate method)
getFirstOutOfStock() {
// YOUR CODE HERE
}
// Get ALL out of stock products
getAllOutOfStock() {
// YOUR CODE HERE
}
// Validate inventory - check if all prices are positive
validatePrices() {
// YOUR CODE HERE
}
}
// TODO 3: Implement StatsCalculator class
class StatsCalculator {
constructor(products) {
this.products = products;
}
// Calculate average price
getAveragePrice() {
// YOUR CODE HERE
}
// Get price range
getPriceRange() {
// Return { min, max }
// YOUR CODE HERE
}
// Calculate average rating
getAverageRating() {
// YOUR CODE HERE
}
// Get top rated products (rating >= 4.5)
getTopRated() {
// YOUR CODE HERE
}
// Get category statistics
getCategoryStats() {
// Return object: { category: { count, avgPrice, avgRating } }
// YOUR CODE HERE
// Hint: Use reduce or Object.keys
}
}💡 Solution Phase 2
/**
* ========================================
* 💻 PHASE 2: Implementation – Giải pháp hoàn chỉnh
* ========================================
*
* ----------------------------------------
* TODO 1: ProductSearch class
* ----------------------------------------
*
* class ProductSearch {
* constructor(products) {
* this.products = products;
* }
*
* // Tìm sản phẩm theo ID → dùng find() (early exit, chỉ cần 1)
* findById(id) {
* return this.products.find(p => p.id === id) ?? null;
* }
*
* // Tìm kiếm theo tên (partial match, case-insensitive) → filter()
* searchByName(query) {
* if (!query?.trim()) return [];
* const lowerQuery = query.toLowerCase().trim();
* return this.products.filter(p =>
* p.name.toLowerCase().includes(lowerQuery)
* );
* }
*
* // Lọc theo khoảng giá → filter()
* filterByPriceRange(min, max) {
* return this.products.filter(p => p.price >= min && p.price <= max);
* }
*
* // Lọc theo tình trạng tồn kho → filter()
* filterByAvailability(inStock) {
* return this.products.filter(p => p.inStock === inStock);
* }
*
* // Lọc theo danh mục → filter()
* filterByCategory(category) {
* if (!category?.trim()) return this.products;
* return this.products.filter(p => p.category === category);
* }
*
* // Tìm kiếm nâng cao – chain nhiều điều kiện
* advancedSearch(criteria = {}) {
* const {
* query,
* minPrice,
* maxPrice,
* inStock,
* category,
* minRating,
* } = criteria;
*
* let result = this.products;
*
* // Chain filter một cách có điều kiện
* if (query?.trim()) {
* const lowerQuery = query.toLowerCase().trim();
* result = result.filter(p =>
* p.name.toLowerCase().includes(lowerQuery)
* );
* }
*
* if (typeof minPrice === 'number') {
* result = result.filter(p => p.price >= minPrice);
* }
*
* if (typeof maxPrice === 'number') {
* result = result.filter(p => p.price <= maxPrice);
* }
*
* if (typeof inStock === 'boolean') {
* result = result.filter(p => p.inStock === inStock);
* }
*
* if (category?.trim()) {
* result = result.filter(p => p.category === category);
* }
*
* if (typeof minRating === 'number') {
* result = result.filter(p => p.rating >= minRating);
* }
*
* return result;
* }
* }
*
* ----------------------------------------
* TODO 2: InventoryManager class
* ----------------------------------------
*
* class InventoryManager {
* constructor(products) {
* this.products = products;
* }
*
* // Có sản phẩm nào hết hàng không? → some() (early exit)
* hasOutOfStock() {
* return this.products.some(p => !p.inStock);
* }
*
* // Tất cả sản phẩm có còn hàng không? → every()
* allInStock() {
* return this.products.every(p => p.inStock);
* }
*
* // Danh mục có sản phẩm nào hết hàng không? → some()
* categoryHasOutOfStock(category) {
* return this.products.some(
* p => p.category === category && !p.inStock
* );
* }
*
* // Lấy sản phẩm hết hàng đầu tiên → find()
* getFirstOutOfStock() {
* return this.products.find(p => !p.inStock) ?? null;
* }
*
* // Lấy tất cả sản phẩm hết hàng → filter()
* getAllOutOfStock() {
* return this.products.filter(p => !p.inStock);
* }
*
* // Kiểm tra tất cả giá có hợp lệ (> 0 và là số) → every()
* validatePrices() {
* return this.products.every(p =>
* typeof p.price === 'number' &&
* p.price > 0 &&
* Number.isFinite(p.price)
* );
* }
* }
*
* ----------------------------------------
* TODO 3: StatsCalculator class
* ----------------------------------------
*
* class StatsCalculator {
* constructor(products) {
* this.products = products;
* }
*
* // Giá trung bình
* getAveragePrice() {
* if (this.products.length === 0) return 0;
* const total = this.products.reduce((sum, p) => sum + p.price, 0);
* return Number((total / this.products.length).toFixed(2));
* }
*
* // Khoảng giá min-max
* getPriceRange() {
* if (this.products.length === 0) return { min: 0, max: 0 };
*
* return this.products.reduce(
* (range, p) => ({
* min: Math.min(range.min, p.price),
* max: Math.max(range.max, p.price),
* }),
* { min: Infinity, max: -Infinity }
* );
* }
*
* // Điểm đánh giá trung bình
* getAverageRating() {
* if (this.products.length === 0) return 0;
* const total = this.products.reduce((sum, p) => sum + p.rating, 0);
* return Number((total / this.products.length).toFixed(1));
* }
*
* // Top sản phẩm có rating >= 4.5, sắp xếp giảm dần
* getTopRated() {
* return this.products
* .filter(p => p.rating >= 4.5)
* .sort((a, b) => b.rating - a.rating);
* }
*
* // Thống kê theo danh mục
* getCategoryStats() {
* return this.products.reduce((stats, p) => {
* const cat = p.category;
* if (!stats[cat]) {
* stats[cat] = {
* count: 0,
* avgPrice: 0,
* avgRating: 0,
* totalPrice: 0,
* totalRating: 0
* };
* }
*
* stats[cat].count += 1;
* stats[cat].totalPrice += p.price;
* stats[cat].totalRating += p.rating;
*
* // Cập nhật trung bình
* stats[cat].avgPrice = Number(
* (stats[cat].totalPrice / stats[cat].count).toFixed(2)
* );
* stats[cat].avgRating = Number(
* (stats[cat].totalRating / stats[cat].count).toFixed(1)
* );
*
* return stats;
* }, {});
* }
* }
*
* const search = new ProductSearch(products);
* console.log(search.searchByName('monitor'));
* console.log(search.advancedSearch({ minPrice: 100, inStock: true }));
*
* const inventory = new InventoryManager(products);
* console.log(inventory.hasOutOfStock());
* console.log(inventory.allInStock());
*
* const stats = new StatsCalculator(products);
* console.log(stats.getAveragePrice());
* console.log(stats.getCategoryStats());
*/// ========================================
// 🧪 PHASE 3: Testing (10 phút)
// ========================================
// Test ProductSearch
const search = new ProductSearch(products);
console.log("=== ProductSearch Tests ===");
console.log("Find ID 3:", search.findById(3));
console.log('Search "monitor":', search.searchByName("monitor"));
console.log("Price 30-100:", search.filterByPriceRange(30, 100));
console.log("In stock:", search.filterByAvailability(true));
console.log("Electronics:", search.filterByCategory("Electronics"));
console.log(
"Advanced search:",
search.advancedSearch({
query: "monitor",
minPrice: 0,
maxPrice: 500,
inStock: true,
minRating: 4.0,
}),
);
// Test InventoryManager
const inventory = new InventoryManager(products);
console.log("\n=== InventoryManager Tests ===");
console.log("Has out of stock?:", inventory.hasOutOfStock());
console.log("All in stock?:", inventory.allInStock());
console.log(
"Electronics has out of stock?:",
inventory.categoryHasOutOfStock("Electronics"),
);
console.log("First out of stock:", inventory.getFirstOutOfStock());
console.log("All out of stock:", inventory.getAllOutOfStock());
console.log("Prices valid?:", inventory.validatePrices());
// Test StatsCalculator
const stats = new StatsCalculator(products);
console.log("\n=== StatsCalculator Tests ===");
console.log("Average price:", stats.getAveragePrice());
console.log("Price range:", stats.getPriceRange());
console.log("Average rating:", stats.getAverageRating());
console.log("Top rated:", stats.getTopRated());
console.log("Category stats:", stats.getCategoryStats());
// 📝 Manual Testing Checklist:
// - [ ] Search returns correct products
// - [ ] Filters work correctly
// - [ ] some() stops early (performance)
// - [ ] every() validates all
// - [ ] Stats calculations are accurate
// - [ ] Edge cases handled (empty results, no matches)⭐⭐⭐⭐⭐ Exercise 5: Production Challenge - Data Processing Pipeline (90 phút)
/**
* 🎯 Mục tiêu: Xây dựng pipeline xử lý dữ liệu sẵn sàng cho production
* ⏱️ Thời gian: 90 phút
*
* 📋 Đặc tả tính năng:
*
* Xây dựng "Bảng điều khiển phân tích doanh số" với các tính năng:
* 1. Import dữ liệu bán hàng từ nhiều nguồn
* 2. Làm sạch & xác thực dữ liệu (Clean & validate data)
* 3. Biến đổi & làm giàu dữ liệu (Transform & enrich)
* 4. Tính toán các chỉ số phân tích (analytics)
* 5. Xuất báo cáo
*
* 🏗️ Tài liệu thiết kế kỹ thuật:
*
* 1. Kiến trúc thành phần:
* - DataImporter: Import & chuẩn hoá dữ liệu
* - DataValidator: Xác thực & làm sạch dữ liệu
* - DataTransformer: Biến đổi & làm giàu dữ liệu
* - AnalyticsEngine: Tính toán các chỉ số
* - ReportGenerator: Tạo báo cáo
*
* 2. Luồng dữ liệu:
* Dữ liệu thô → Import → Validate → Transform → Analyze → Report
*
* 3. Các yếu tố cần cân nhắc về hiệu năng:
* - Sử dụng các phương thức array phù hợp (tránh lặp không cần thiết)
* - Cache các phép tính khi có thể
* - Xử lý tập dữ liệu lớn (1000+ bản ghi)
*
* 4. Chiến lược xử lý lỗi:
* - Xác thực tất cả input
* - Thoái lui an toàn (graceful degradation)
* - Thông báo lỗi rõ ràng, dễ hiểu
*
* ✅ Checklist production:
* - [ ] Xử lý các edge case (dữ liệu rỗng, dữ liệu không hợp lệ)
* - [ ] Xác thực input
* - [ ] Xử lý lỗi
* - [ ] Tối ưu hiệu năng
* - [ ] Tài liệu hoá code
* - [ ] Các kịch bản unit test
*/
// ========================================
// SAMPLE DATA
// ========================================
const rawSalesData = [
{
date: "2024-01-01",
product: "Laptop",
quantity: 2,
price: 1000,
customer: "John Doe",
region: "West",
},
{
date: "2024-01-01",
product: "Mouse",
quantity: 5,
price: 25,
customer: "Jane Smith",
region: "East",
},
{
date: "2024-01-02",
product: "Keyboard",
quantity: 3,
price: 75,
customer: "Bob Johnson",
region: "West",
},
{
date: "2024-01-02",
product: "Monitor",
quantity: 1,
price: 300,
customer: "Alice Brown",
region: "North",
},
{
date: "2024-01-03",
product: "Laptop",
quantity: 1,
price: 1000,
customer: "Charlie Wilson",
region: "South",
},
{
date: "2024-01-03",
product: "Mouse",
quantity: 10,
price: 25,
customer: "Diana Moore",
region: "East",
},
// Các bản ghi không hợp lệ để kiểm thử
{
date: null,
product: "Cable",
quantity: -5,
price: 15,
customer: "",
region: "West",
},
{
date: "2024-01-04",
product: "",
quantity: 0,
price: 0,
customer: "Test",
region: "",
},
];
// ========================================
// TODO: IMPLEMENT THE SYSTEM
// ========================================
/**
* Class 1: DataImporter
* Responsibility: Import và normalize data từ nhiều nguồn
*/
class DataImporter {
// Import từ array
importFromArray(data) {
// YOUR CODE HERE
// - Validate input is array
// - Normalize field names (lowercase, trim)
// - Return normalized data
}
// Import từ CSV string (bonus)
importFromCSV(csvString) {
// YOUR CODE HERE
// - Parse CSV
// - Convert to array of objects
// - Normalize
}
}
/**
* Class 2: DataValidator
* Responsibility: Validate và clean data
*/
class DataValidator {
constructor() {
this.validationErrors = [];
}
// Validate single record
validateRecord(record) {
// YOUR CODE HERE
// Rules:
// - date: required, valid date format
// - product: required, non-empty string
// - quantity: required, positive number
// - price: required, positive number
// - customer: required, non-empty string
// - region: required, one of [North, South, East, West]
// Return: { isValid: boolean, errors: [] }
}
// Validate all records
validateAll(data) {
// YOUR CODE HERE
// - Validate each record
// - Collect all errors
// - Return: { validRecords: [], invalidRecords: [], errors: [] }
}
// Clean data - remove invalid, fill defaults
clean(data, options = {}) {
// YOUR CODE HERE
// options: { removeInvalid: true, fillDefaults: false }
}
}
/**
* Class 3: DataTransformer
* Responsibility: Transform và enrich data
*/
class DataTransformer {
// Add calculated fields
addCalculatedFields(data) {
// YOUR CODE HERE
// Add fields:
// - revenue: quantity * price
// - date: convert to Date object
// - month: extract month
// - dayOfWeek: extract day of week
}
// Group by key
groupBy(data, key) {
// YOUR CODE HERE
// Return: { [key]: [...records] }
}
// Aggregate data
aggregate(data, groupKey, aggregations) {
// YOUR CODE HERE
// Example aggregations:
// { sum: 'revenue', avg: 'price', count: '*' }
}
}
/**
* Class 4: AnalyticsEngine
* Responsibility: Calculate metrics và insights
*/
class AnalyticsEngine {
constructor(data) {
this.data = data;
}
// Calculate total revenue
getTotalRevenue() {
// YOUR CODE HERE
}
// Calculate revenue by product
getRevenueByProduct() {
// YOUR CODE HERE
// Return: { product: revenue }
}
// Calculate revenue by region
getRevenueByRegion() {
// YOUR CODE HERE
}
// Calculate revenue by date
getRevenueByDate() {
// YOUR CODE HERE
}
// Get top products
getTopProducts(n = 5) {
// YOUR CODE HERE
// Return: [{ product, revenue }] sorted by revenue
}
// Get top customers
getTopCustomers(n = 5) {
// YOUR CODE HERE
}
// Get sales trends
getSalesTrends() {
// YOUR CODE HERE
// Return: { dailyAverage, trend: 'up'|'down'|'stable' }
}
// Calculate conversion metrics
getMetrics() {
// YOUR CODE HERE
// Return: {
// totalRevenue,
// totalOrders,
// averageOrderValue,
// topProduct,
// topRegion
// }
}
}
/**
* Class 5: ReportGenerator
* Responsibility: Generate formatted reports
*/
class ReportGenerator {
constructor(analyticsEngine) {
this.analytics = analyticsEngine;
}
// Generate summary report
generateSummary() {
// YOUR CODE HERE
// Return formatted string
}
// Generate detailed report
generateDetailedReport() {
// YOUR CODE HERE
}
// Export to JSON
exportToJSON() {
// YOUR CODE HERE
}
// Export to CSV
exportToCSV() {
// YOUR CODE HERE
}
}
// ========================================
// MAIN PIPELINE
// ========================================
/**
* Main function - orchestrate the entire pipeline
*/
function processSalesData(rawData) {
console.log("=== Sales Analytics Pipeline ===\n");
// Step 1: Import
console.log("Step 1: Importing data...");
const importer = new DataImporter();
const imported = importer.importFromArray(rawData);
console.log(`Imported ${imported.length} records\n`);
// Step 2: Validate
console.log("Step 2: Validating data...");
const validator = new DataValidator();
const validation = validator.validateAll(imported);
console.log(`Valid: ${validation.validRecords.length}`);
console.log(`Invalid: ${validation.invalidRecords.length}`);
if (validation.errors.length > 0) {
console.log("Errors:", validation.errors.slice(0, 3), "...\n");
}
// Step 3: Clean
console.log("Step 3: Cleaning data...");
const cleaned = validator.clean(imported, { removeInvalid: true });
console.log(`Cleaned: ${cleaned.length} records\n`);
// Step 4: Transform
console.log("Step 4: Transforming data...");
const transformer = new DataTransformer();
const transformed = transformer.addCalculatedFields(cleaned);
console.log("Added calculated fields\n");
// Step 5: Analyze
console.log("Step 5: Analyzing data...");
const analytics = new AnalyticsEngine(transformed);
const metrics = analytics.getMetrics();
console.log("Metrics:", metrics, "\n");
// Step 6: Report
console.log("Step 6: Generating reports...");
const reporter = new ReportGenerator(analytics);
const summary = reporter.generateSummary();
console.log(summary);
return {
rawCount: rawData.length,
validCount: cleaned.length,
metrics,
reports: {
summary,
detailed: reporter.generateDetailedReport(),
},
};
}
// Run pipeline
const result = processSalesData(rawSalesData);
// ========================================
// 📝 DOCUMENTATION
// ========================================
/**
* README.md
*
* # Sales Analytics Pipeline
*
* ## Tính năng
* - Import dữ liệu từ nhiều nguồn
* - Xác thực và làm sạch dữ liệu
* - Biến đổi và làm giàu dữ liệu
* - Tính toán các chỉ số phân tích
* - Tạo báo cáo
*
* ## Cách sử dụng
* ```javascript
* const result = processSalesData(rawData);
* console.log(result.metrics);
* ```
*
* ## Tài liệu API
* [Tài liệu hoá từng class và method]
*
* ## Ghi chú về hiệu năng
* - Xử lý hiệu quả lên đến 10.000 bản ghi
* - Sử dụng các phương thức array phù hợp để giảm số lần lặp
* - Cache các giá trị đã được tính toán
*
* ## Xử lý lỗi
* - Xác thực tất cả input
* - Cung cấp thông báo lỗi chi tiết
* - Thoái lui an toàn (graceful degradation) khi dữ liệu không hợp lệ
*/
// ========================================
// 🔍 CODE REVIEW SELF-CHECKLIST
// ========================================
/**
* - [ ] Tất cả class đã được implement
* - [ ] Có validate input trong tất cả method
* - [ ] Xử lý lỗi cho các edge case
* - [ ] Sử dụng phương thức array phù hợp
* - [ ] Đã tối ưu hiệu năng (không có vòng lặp dư thừa)
* - [ ] Code có tài liệu hoá
* - [ ] Tên biến rõ ràng, dễ hiểu
* - [ ] Function thuần (pure function) khi có thể
* - [ ] Không có side effect trong các phép tính
* - [ ] Test pass với cả dữ liệu hợp lệ và không hợp lệ
*/💡 Solution Sales Analytics Pipeline (Production-ready)
/**
* Sales Analytics Pipeline - Production-ready implementation
* Features: Import → Validate → Clean → Transform → Analyze → Report
* Performance: Optimized with single-pass where possible, appropriate array methods
* Error handling: Graceful, detailed errors, invalid data skipped or defaulted
*/
/**
* DataImporter: Nhập và chuẩn hóa dữ liệu từ nhiều nguồn
*/
class DataImporter {
/**
* Import từ mảng object
* @param {Array} data - Raw sales data
* @returns {Array} Normalized records
*/
importFromArray(data) {
if (!Array.isArray(data)) {
throw new Error('Input must be an array');
}
return data.map((record) => {
const normalized = {};
for (const [key, value] of Object.entries(record)) {
const lowerKey = key.toLowerCase().trim();
normalized[lowerKey] = typeof value === 'string' ? value.trim() : value;
}
return normalized;
});
}
/**
* Import từ chuỗi CSV
* @param {string} csvString - Nội dung CSV (có header ở dòng đầu)
* @param {Object} [options] - Tùy chọn parse
* @param {string} [options.delimiter=','] - Dấu phân cách cột
* @param {boolean} [options.skipEmptyLines=true] - Bỏ qua dòng trống
* @returns {Array<Object>} Mảng các record đã normalize (key lowercase)
* @throws {Error} Nếu csvString không hợp lệ
*/
importFromCSV(csvString, options = {}) {
if (typeof csvString !== 'string' || !csvString.trim()) {
throw new Error('CSV input must be a non-empty string');
}
const {
delimiter = ',',
skipEmptyLines = true,
} = options;
// Tách thành các dòng
const lines = csvString.split(/\r?\n/);
if (lines.length < 1) {
throw new Error('CSV is empty - no header found');
}
// Lấy header từ dòng đầu tiên
const headers = this.#parseCSVLine(lines[0], delimiter)
.map(h => h.toLowerCase().trim().replace(/\s+/g, '_')); // snake_case cho key đẹp hơn
if (headers.length === 0) {
throw new Error('No headers found in CSV');
}
const records = [];
// Bắt đầu từ dòng 1 (bỏ header)
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
// Bỏ qua dòng trống nếu bật tùy chọn
if (skipEmptyLines && !line) continue;
const values = this.#parseCSVLine(line, delimiter);
// Nếu số cột không khớp header → log warning nhưng vẫn xử lý (graceful)
if (values.length !== headers.length) {
console.warn(`Row ${i + 1} has mismatched columns (${values.length} vs ${headers.length})`);
continue; // hoặc có thể fill missing bằng undefined
}
const record = {};
headers.forEach((header, idx) => {
let value = values[idx]?.trim();
// Cố gắng convert số nếu có thể
if (value !== '' && !isNaN(value) && !isNaN(parseFloat(value))) {
value = parseFloat(value);
}
record[header] = value;
});
records.push(record);
}
console.log(`Imported ${records.length} records from CSV`);
return records;
}
/**
* Helper: Parse một dòng CSV, hỗ trợ giá trị có dấu ngoặc kép
* @private
*/
#parseCSVLine(line, delimiter) {
const result = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
inQuotes = !inQuotes;
continue;
}
if (char === delimiter && !inQuotes) {
result.push(current);
current = '';
continue;
}
current += char;
}
// Push phần cuối
result.push(current);
// Vì vòng for chỉ push khi gặp delimiter,
// nhưng giá trị cuối cùng KHÔNG có delimiter phía sau.
// Nếu không push ở cuối → mất cột cuối cùng.
return result;
}
// ========================================
// Ví dụ sử dụng
// ========================================
const sampleCSV = `
date,product,quantity,price,customer,region
2024-01-01,Laptop,2,1000,John Doe,West
2024-01-01,Mouse,5,25,Jane Smith,East
"2024-01-02","Keyboard, Pro",3,75,Bob Johnson,West
2024-01-02,Monitor,1,300,Alice Brown,North
`;
const importer = new DataImporter();
const csvData = importer.importFromCSV(sampleCSV);
console.log(csvData[0]);
// → { date: '2024-01-01', product: 'Laptop', quantity: 2, price: 1000, customer: 'John Doe', region: 'West' }
}
/**
* DataValidator: Kiểm tra và làm sạch dữ liệu
*/
class DataValidator {
constructor() {
this.validationErrors = [];
}
/**
* Validate một record
* @returns {{isValid: boolean, errors: string[]}}
*/
validateRecord(record) {
const errors = [];
// date: required, valid ISO date
if (!record.date || isNaN(Date.parse(record.date))) {
errors.push('Invalid or missing date');
}
// product: required, non-empty
if (
!record.product ||
typeof record.product !== 'string' ||
record.product.trim() === ''
) {
errors.push('Invalid or missing product name');
}
// quantity: positive integer
if (
typeof record.quantity !== 'number' ||
!Number.isInteger(record.quantity) ||
record.quantity <= 0
) {
errors.push('Quantity must be positive integer');
}
// price: positive number
if (
typeof record.price !== 'number' ||
record.price <= 0 ||
!Number.isFinite(record.price)
) {
errors.push('Price must be positive number');
}
// customer: required, non-empty
if (
!record.customer ||
typeof record.customer !== 'string' ||
record.customer.trim() === ''
) {
errors.push('Invalid or missing customer name');
}
// region: required, one of allowed values
const validRegions = ['north', 'south', 'east', 'west'];
if (!record.region || !validRegions.includes(record.region.toLowerCase())) {
errors.push(`Region must be one of: ${validRegions.join(', ')}`);
}
return {
isValid: errors.length === 0,
errors,
};
}
/**
* Validate toàn bộ dataset
*/
validateAll(data) {
this.validationErrors = [];
const validRecords = [];
const invalidRecords = [];
data.forEach((record, index) => {
const { isValid, errors } = this.validateRecord(record);
if (isValid) {
validRecords.push(record);
} else {
invalidRecords.push(record);
this.validationErrors.push({
index,
record,
errors,
});
}
});
return {
validRecords,
invalidRecords,
errors: this.validationErrors,
};
}
/**
* Clean data: loại bỏ invalid hoặc fill default tùy options
*/
clean(data, options = { removeInvalid: true, fillDefaults: false }) {
if (options.removeInvalid) {
const validation = this.validateAll(data);
return validation.validRecords;
}
// Nếu không remove, có thể fill default (chưa implement chi tiết ở đây)
return data;
}
}
/**
* DataTransformer: Biến đổi và enrich data
*/
class DataTransformer {
/**
* Thêm các trường tính toán
* @returns {Array} data với các field mới
*/
addCalculatedFields(data) {
return data.map((record) => {
const dateObj = new Date(record.date);
return {
...record,
revenue: record.quantity * record.price,
dateObj,
month: dateObj.getMonth() + 1, // 1-12
dayOfWeek: dateObj.toLocaleString('en-US', { weekday: 'short' }),
};
});
}
/**
* Group by key
*/
groupBy(data, key) {
return data.reduce((acc, item) => {
const groupKey = item[key];
if (!acc[groupKey]) acc[groupKey] = [];
acc[groupKey].push(item);
return acc;
}, {});
}
/**
* Aggregate theo groupKey với các phép tính
*/
aggregate(data, groupKey, aggregations = {}) {
const groups = this.groupBy(data, groupKey);
return Object.fromEntries(
Object.entries(groups).map(([key, items]) => {
const result = { [groupKey]: key, count: items.length };
if (aggregations.sum) {
result[`sum_${aggregations.sum}`] = items.reduce(
(sum, i) => sum + (i[aggregations.sum] || 0),
0
);
}
if (aggregations.avg) {
const total = items.reduce(
(sum, i) => sum + (i[aggregations.avg] || 0),
0
);
result[`avg_${aggregations.avg}`] = items.length
? total / items.length
: 0;
}
return [key, result];
})
);
}
}
/**
* AnalyticsEngine: Tính toán các chỉ số kinh doanh
*/
class AnalyticsEngine {
constructor(data) {
this.data = data;
}
getTotalRevenue() {
return this.data.reduce((sum, r) => sum + r.revenue, 0);
}
getRevenueByProduct() {
const grouped = new DataTransformer().groupBy(this.data, 'product');
return Object.fromEntries(
Object.entries(grouped).map(([product, items]) => [
product,
items.reduce((sum, i) => sum + i.revenue, 0),
])
);
}
getRevenueByRegion() {
const grouped = new DataTransformer().groupBy(this.data, 'region');
return Object.fromEntries(
Object.entries(grouped).map(([region, items]) => [
region,
items.reduce((sum, i) => sum + i.revenue, 0),
])
);
}
getTopProducts(n = 5) {
const revenueByProduct = this.getRevenueByProduct();
return Object.entries(revenueByProduct)
.map(([product, revenue]) => ({ product, revenue }))
.sort((a, b) => b.revenue - a.revenue)
.slice(0, n);
}
getMetrics() {
const totalRevenue = this.getTotalRevenue();
const totalOrders = this.data.length;
const averageOrderValue = totalOrders ? totalRevenue / totalOrders : 0;
const revenueByProduct = this.getRevenueByProduct();
const topProduct = Object.entries(revenueByProduct).reduce(
(max, [p, rev]) =>
rev > max.revenue ? { product: p, revenue: rev } : max,
{ revenue: -Infinity }
);
const revenueByRegion = this.getRevenueByRegion();
const topRegion = Object.entries(revenueByRegion).reduce(
(max, [r, rev]) =>
rev > max.revenue ? { region: r, revenue: rev } : max,
{ revenue: -Infinity }
);
return {
totalRevenue,
totalOrders,
averageOrderValue: Number(averageOrderValue.toFixed(2)),
topProduct,
topRegion,
};
}
}
/**
* ReportGenerator: Tạo báo cáo định dạng
*/
class ReportGenerator {
constructor(analytics) {
this.analytics = analytics;
}
generateSummary() {
const m = this.analytics.getMetrics();
return `
=== Sales Analytics Summary ===
Total Revenue : $${m.totalRevenue.toLocaleString()}
Total Orders : ${m.totalOrders}
Avg Order Value : $${m.averageOrderValue}
Top Product : ${
m.topProduct.product
} ($${m.topProduct.revenue.toLocaleString()})
Top Region : ${
m.topRegion.region
} ($${m.topRegion.revenue.toLocaleString()})
`.trim();
}
exportToJSON() {
return JSON.stringify(this.analytics.getMetrics(), null, 2);
}
}
/**
* Main pipeline orchestration
*/
function processSalesData(rawData) {
console.log('=== Sales Analytics Pipeline ===\n');
// 1. Import
const importer = new DataImporter();
const imported = importer.importFromArray(rawData);
console.log(`Imported ${imported.length} records`);
// 2. Validate & Clean
const validator = new DataValidator();
const validation = validator.validateAll(imported);
console.log(
`Valid: ${validation.validRecords.length} | Invalid: ${validation.invalidRecords.length}`
);
const cleaned = validator.clean(imported, { removeInvalid: true });
console.log(`After cleaning: ${cleaned.length} records\n`);
// 3. Transform
const transformer = new DataTransformer();
const transformed = transformer.addCalculatedFields(cleaned);
console.log(
'Transform completed (added revenue, dateObj, month, dayOfWeek)\n'
);
// 4. Analyze
const analytics = new AnalyticsEngine(transformed);
const metrics = analytics.getMetrics();
console.log('Key Metrics:', metrics, '\n');
// 5. Report
const reporter = new ReportGenerator(analytics);
const summary = reporter.generateSummary();
console.log(summary);
return {
rawCount: rawData.length,
validCount: cleaned.length,
invalidCount: validation.invalidRecords.length,
metrics,
summaryReport: summary,
};
}
// Chạy thử
const result = processSalesData(rawSalesData);
/*Giải pháp này:
* - Đáp ứng đầy đủ 5 class theo thiết kế
* - Xử lý validation nghiêm ngặt, clean dữ liệu invalid
* - Tối ưu performance: sử dụng reduce/groupBy hiệu quả, tránh lặp thừa
* - Có error handling cơ bản + logging rõ ràng
* - Dễ mở rộng (CSV, thêm metrics, cache, etc.)
*/📊 PHẦN 4: SO SÁNH PATTERNS (30 phút)
Bảng So Sánh Trade-offs
1. Promises vs Callbacks vs async/await
| Aspect | Callbacks | Promises | async/await |
|---|---|---|---|
| Readability | ⭐ (Pyramid of doom) | ⭐⭐⭐ (Chainable) | ⭐⭐⭐⭐⭐ (Sequential) |
| Error Handling | ⭐ (Try-catch mỗi callback) | ⭐⭐⭐ (Centralized .catch) | ⭐⭐⭐⭐⭐ (Try-catch block) |
| Debugging | ⭐ (Khó trace) | ⭐⭐⭐ (Better stack traces) | ⭐⭐⭐⭐⭐ (Linear execution) |
| Learning Curve | ⭐⭐ (Simple concept) | ⭐⭐⭐⭐ (Need to understand Promise) | ⭐⭐⭐⭐⭐ (Easiest once learned) |
| Browser Support | ⭐⭐⭐⭐⭐ (All) | ⭐⭐⭐⭐ (IE11+) | ⭐⭐⭐⭐ (Modern browsers) |
| Use Case | Legacy code | Modern async code | Preferred for new code |
When to use:
- Callbacks: ❌ Avoid (legacy support only)
- Promises: ✅ When need fine-grained control, chaining
- async/await: ✅ Default choice (clearest code)
2. Default Export vs Named Export
| Aspect | Default Export | Named Export |
|---|---|---|
| Naming Freedom | ✅ Import with any name | ❌ Must use exact name (or alias) |
| Import Syntax | import User from './User' | import { User } from './User' |
| Multiple Exports | ❌ Only 1 per file | ✅ Multiple per file |
| Refactoring | ❌ Harder (name can vary) | ✅ Easier (consistent names) |
| Tree Shaking | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ (Better) |
| Use Case | Main export (component, class) | Utilities, constants |
Best Practices:
// ✅ GOOD: Clear, predictable
// Button.js
export default function Button() {} // Main component
// utils.js
export function formatDate() {} // Utility
export function formatCurrency() {}
// ❌ AVOID: Confusing
// SomeFile.js
export default { a, b, c }; // What is the main thing?3. Optional Chaining (?.) vs Traditional Checks
| Aspect | Traditional | Optional Chaining |
|---|---|---|
| Code Length | ❌ Very verbose | ✅ Concise |
| Readability | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| Performance | ⭐⭐⭐⭐⭐ (Slightly faster) | ⭐⭐⭐⭐ (Negligible difference) |
| Browser Support | ⭐⭐⭐⭐⭐ (All) | ⭐⭐⭐⭐ (ES2020+) |
| Maintenance | ❌ Error-prone | ✅ Safer |
// Traditional
if (user && user.profile && user.profile.address) {
const city = user.profile.address.city;
}
// Optional Chaining
const city = user?.profile?.address?.city;4. Nullish Coalescing (??) vs OR (||)
| Value | || Result | ?? Result |
|---|---|---|
null | 'default' | 'default' |
undefined | 'default' | 'default' |
false | 'default' ❌ | false ✅ |
0 | 'default' ❌ | 0 ✅ |
'' | 'default' ❌ | '' ✅ |
'value' | 'value' | 'value' |
Decision Rule:
┌─────────────────────────────────┐
│ Need default value? │
└────────┬────────────────────────┘
│
├─ Want to keep 0, false, '' as valid?
│ → Use ?? (Nullish Coalescing)
│
└─ Want to replace ALL falsy values?
→ Use || (OR operator)Decision Tree: Chọn Array Method
┌─────────────────────────────────────────┐
│ Cần xử lý array như thế nào? │
└────────┬────────────────────────────────┘
│
├─ Tìm 1 element?
│ ├─ Trả về element → find()
│ └─ Trả về index → findIndex()
│
├─ Check điều kiện?
│ ├─ Check ÍT NHẤT 1 → some()
│ └─ Check TẤT CẢ → every()
│
├─ Lọc elements?
│ ├─ Lấy 1 element → find()
│ └─ Lấy nhiều elements → filter()
│
├─ Transform array?
│ ├─ 1-1 mapping → map()
│ ├─ Flatten array → flatMap()
│ └─ Combine to 1 value → reduce()
│
└─ Loop mà không return?
→ forEach()Performance Tips:
| Operation | ❌ Avoid | ✅ Prefer |
|---|---|---|
| Find 1 element | filter()[0] | find() |
| Check existence | filter().length > 0 | some() |
| Validate all | filter().length === arr.length | every() |
| Multiple operations | Chain multiple loops | Combine in 1 loop |
🧪 PHẦN 5: DEBUG LAB (20 phút)
Bug 1: Promise Not Awaited
// 🐛 BUG: Function không chờ Promise complete
async function fetchUserData(userId) {
const user = fetchUser(userId); // ❌ Quên await
console.log(user.name); // ❌ Cannot read property 'name' of Promise
return user;
}
// ❓ QUESTIONS:
// 1. Tại sao `user.name` bị undefined?
// 2. `user` là gì ở đây?
// 3. Fix như thế nào?
// ✅ SOLUTION:
async function fetchUserDataFixed(userId) {
const user = await fetchUser(userId); // ✅ Thêm await
console.log(user.name); // ✅ Works now
return user;
}
// 🎓 LESSON:
// - async function KHÔNG TỰ ĐỘNG await
// - Phải explicitly dùng await keyword
// - Promise là object đặc biệt, không phải data
// - Debug: Check typeof, log giá trị để phát hiện Promise
// 🛡️ PREVENTION:
// - Luôn await Promise trong async function
// - Use ESLint rule: @typescript-eslint/no-floating-promises
// - TypeScript sẽ warn nếu không awaitBug 2: OR vs Nullish Coalescing
// 🐛 BUG: Sử dụng || thay vì ??
const config = {
port: 0, // ✅ Port 0 is valid!
debug: false, // ✅ false is valid!
retries: null, // ❌ Should use default
};
function getConfig(config) {
return {
port: config.port || 3000, // 🐛 BUG: 0 → 3000
debug: config.debug || false, // 🐛 BUG: false → false (OK nhưng misleading)
retries: config.retries || 3, // ✅ null → 3 (OK)
};
}
console.log(getConfig(config));
// { port: 3000, debug: false, retries: 3 }
// ❌ port should be 0!
// ❓ QUESTIONS:
// 1. Tại sao port thành 3000 thay vì 0?
// 2. Khi nào nên dùng || vs ??
// 3. false || false trả về gì?
// ✅ SOLUTION:
function getConfigFixed(config) {
return {
port: config.port ?? 3000, // ✅ 0 → 0
debug: config.debug ?? false, // ✅ false → false
retries: config.retries ?? 3, // ✅ null → 3
};
}
console.log(getConfigFixed(config));
// { port: 0, debug: false, retries: 3 } ✅
// 🎓 LESSON:
// - || checks falsy (null, undefined, false, 0, '', NaN)
// - ?? checks nullish (null, undefined ONLY)
// - 0, false, '' are VALID values in many cases
// - Use ?? as default for object properties
// 🛡️ PREVENTION:
// - Default to ?? unless specifically want to replace falsy
// - Document when || is intentional
// - Consider if 0, false, '' are valid for the use caseBug 3: Optional Chaining with Function Calls
// 🐛 BUG: Incorrect optional chaining syntax
const user = {
name: "John",
getProfile: () => ({ bio: "Developer" }),
};
// ❌ WRONG: Optional chaining on property, not call
const bio1 = user.getProfile?.()?.bio; // Works, but...
const bio2 = user.getSettings?.()?.theme; // undefined (OK)
const bio3 = user?.getProfile()?.bio; // 🐛 Will throw if getProfile doesn't exist
// ❓ QUESTIONS:
// 1. Khác nhau giữa obj.method?.() và obj?.method()?
// 2. Khi nào nên dùng cái nào?
// 3. bio3 sẽ crash khi nào?
// ✅ SOLUTION:
// RULE: ?. goes BEFORE ()
const bio1Fixed = user.getProfile?.()?.bio; // ✅ Safe call
const bio2Fixed = user.getSettings?.()?.theme; // ✅ Safe call
const bio3Fixed = user?.getProfile?.()?.bio; // ✅ Safest
// 🎓 LESSON:
// Method call optional chaining:
// - obj.method?.() - Call if method exists
// - obj?.method() - Access obj, then call (WILL throw if method doesn't exist)
// - obj?.method?.() - Safest (check both obj and method)
// 🛡️ PREVENTION:
// - Always use ?. before () for optional method calls
// - Chain ?. for nested optional access
// - Test with null/undefined objects✅ PHẦN 6: TỰ ĐÁNH GIÁ (15 phút)
Knowledge Check
- [ ] Tôi hiểu khác nhau giữa Callback, Promise, và async/await
- [ ] Tôi biết khi nào dùng default vs named export
- [ ] Tôi có thể giải thích cách ?. hoạt động
- [ ] Tôi biết khác nhau giữa ?? và ||
- [ ] Tôi biết khi nào dùng find() vs filter()
- [ ] Tôi biết khi nào dùng some() vs every()
- [ ] Tôi có thể sử dụng Object.keys/values/entries
- [ ] Tôi hiểu performance implications của array methods
- [ ] Tôi có thể handle nested data safely
- [ ] Tôi biết cách debug Promise issuesCode Review Checklist
#### ES6 Modules
- [ ] Export type phù hợp (default vs named)
- [ ] Import statements rõ ràng
- [ ] Không có circular dependencies
- [ ] File structure logic
#### Async Code
- [ ] Promises được await đúng cách
- [ ] Error handling với try-catch
- [ ] Không có floating promises
- [ ] Appropriate use of Promise.all for parallel
#### Safe Access
- [ ] Sử dụng ?. cho nested access
- [ ] Sử dụng ?? cho defaults (not ||)
- [ ] Handle null/undefined cases
- [ ] Preserve valid falsy values (0, false, '')
#### Array Methods
- [ ] Chọn đúng method cho use case
- [ ] Không có unnecessary iterations
- [ ] Early termination khi có thể (some, every, find)
- [ ] Pure functions (no mutations)
#### General
- [ ] Code readable and maintainable
- [ ] Edge cases handled
- [ ] Variables named clearly
- [ ] Comments cho complex logic🏠 BÀI TẬP VỀ NHÀ
Bắt buộc (30 phút)
Bài 1: Promise Chain Practice
/**
* Tạo một series of promises simulate multi-step process:
* 1. validateUser(userId) - Check user hợp lệ
* 2. fetchUserPermissions(userId) - Lấy permissions
* 3. checkAccess(permissions, resource) - Check có quyền không
*
* Requirements:
* - Chain promises properly
* - Handle errors at each step
* - Return final result
*/💡 Solution
/**
* Bài 1: Promise Chain Practice - Multi-step User Access Check
* Mô phỏng quy trình kiểm tra quyền truy cập theo thứ tự:
* 1. Validate user
* 2. Lấy permissions
* 3. Kiểm tra quyền truy cập resource
*
* Yêu cầu:
* - Chain promise đúng cách (.then / .catch)
* - Xử lý lỗi ở từng bước
* - Trả về kết quả cuối cùng hoặc throw error
*/
// Hàm giả lập (simulate async operations)
function validateUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!userId || typeof userId !== "string" || userId.trim() === "") {
reject(new Error("Invalid userId: must be a non-empty string"));
} else if (userId === "invalid_user") {
reject(new Error("User not found or banned"));
} else {
console.log(`User ${userId} validated`);
resolve({ userId, status: "active" });
}
}, 800);
});
}
function fetchUserPermissions(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Giả lập lấy permissions từ DB/API
const permissionsDb = {
user123: ["read:profile", "write:posts", "delete:own"],
admin001: ["read:*", "write:*", "delete:*", "manage:users"],
guest999: ["read:public"],
};
const perms = permissionsDb[userId];
if (!perms) {
reject(new Error("No permissions found for this user"));
} else {
console.log(`Permissions loaded for ${userId}`);
resolve(perms);
}
}, 600);
});
}
function checkAccess(permissions, resource) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resource ví dụ: 'posts:edit', 'users:delete', 'profile:read'
const requiredPerm = resource.split(":")[0] + ":*"; // wildcard check
const hasAccess =
permissions.includes(resource) ||
permissions.includes(requiredPerm) ||
permissions.includes("admin:*");
if (hasAccess) {
console.log(`Access granted to ${resource}`);
resolve({ granted: true, resource });
} else {
reject(new Error(`Access denied to ${resource}`));
}
}, 400);
});
}
/**
* Hàm chính: checkUserAccess
* Chain toàn bộ quy trình và xử lý lỗi tập trung
* @param {string} userId
* @param {string} resource - ví dụ: 'posts:edit'
* @returns {Promise<{granted: boolean, resource: string}>}
*/
function checkUserAccess(userId, resource) {
console.log(`\n=== Checking access for user: ${userId} to ${resource} ===`);
return validateUser(userId)
.then((user) => {
// Bước 1 thành công → lấy permissions
return fetchUserPermissions(user.userId);
})
.then((permissions) => {
// Bước 2 thành công → check quyền
return checkAccess(permissions, resource);
})
.then((result) => {
// Thành công toàn bộ
console.log("Final result: Access granted ✓");
return result;
})
.catch((error) => {
// Xử lý lỗi tập trung
console.error("Access check failed:", error.message);
throw error; // ném tiếp để caller xử lý nếu cần
});
}
// ========================================
// TEST CASES
// ========================================
// Test 1: Thành công
checkUserAccess("user123", "posts:edit")
.then((result) => console.log("Result:", result))
.catch((err) => console.log("Caught:", err.message));
// Test 2: User không tồn tại
checkUserAccess("invalid_user", "profile:read").catch((err) =>
console.log("Expected error:", err.message),
);
// Test 3: Không có quyền
checkUserAccess("guest999", "posts:delete").catch((err) =>
console.log("Expected denied:", err.message),
);
// Test 4: Admin full quyền
checkUserAccess("admin001", "users:delete").then((result) =>
console.log("Admin access:", result),
);
// Bonus: Dùng async/await (cách viết hiện đại hơn, dễ đọc)
async function checkUserAccessAsync(userId, resource) {
try {
const user = await validateUser(userId);
const permissions = await fetchUserPermissions(user.userId);
const access = await checkAccess(permissions, resource);
console.log("Async success:", access);
return access;
} catch (error) {
console.error("Async failed:", error.message);
throw error;
}
}
// Chạy thử async version
// checkUserAccessAsync('user123', 'write:posts');Bài 2: Module Organization
/**
* Refactor code sau thành module structure hợp lý:
* - Tạo separate files cho utilities
* - Use appropriate export types
* - Re-export từ index
*/💡 Solution
/**
* ========================================
* 📁 Cấu trúc thư mục đề xuất
* ========================================
*
* src/
* ├── utils/
* │ ├── validators.js // các hàm validate
* │ └── formatters.js // (nếu cần format sau này)
* ├── auth/
* │ ├── validateUser.js
* │ ├── fetchPermissions.js
* │ └── checkAccess.js
* ├── accessChecker.js // hàm chính chain các bước
* └── index.js // public API, re-export
*//**
* ========================================
* 1. src/auth/validateUser.js
* ========================================
*/
/**
* Validate user ID
*
* @param {string} userId
* @returns {Promise<{ userId: string, status: string }>}
* @throws {Error}
*/
export async function validateUser(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!userId || typeof userId !== "string" || userId.trim() === "") {
reject(new Error("Invalid userId: must be a non-empty string"));
} else if (userId === "invalid_user") {
reject(new Error("User not found or banned"));
} else {
console.log(`User ${userId} validated`);
resolve({ userId, status: "active" });
}
}, 800);
});
}/**
* ========================================
* 2. src/auth/fetchPermissions.js
* ========================================
*/
/**
* Fetch permissions for a user
*
* @param {string} userId
* @returns {Promise<string[]>}
* @throws {Error}
*/
export async function fetchUserPermissions(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const permissionsDb = {
user123: ["read:profile", "write:posts", "delete:own"],
admin001: ["read:*", "write:*", "delete:*", "manage:users"],
guest999: ["read:public"],
};
const perms = permissionsDb[userId];
if (!perms) {
reject(new Error("No permissions found for this user"));
} else {
console.log(`Permissions loaded for ${userId}`);
resolve(perms);
}
}, 600);
});
}/**
* ========================================
* 3. src/auth/checkAccess.js
* ========================================
*/
/**
* Check if user has access to a resource
*
* @param {string[]} permissions
* @param {string} resource
* @returns {Promise<{ granted: boolean, resource: string }>}
* @throws {Error}
*/
export async function checkAccess(permissions, resource) {
return new Promise((resolve, reject) => {
setTimeout(() => {
const requiredPerm = resource.split(":")[0] + ":*";
const hasAccess =
permissions.includes(resource) ||
permissions.includes(requiredPerm) ||
permissions.some((p) => p === "admin:*" || p === requiredPerm);
if (hasAccess) {
console.log(`Access granted to ${resource}`);
resolve({ granted: true, resource });
} else {
reject(new Error(`Access denied to ${resource}`));
}
}, 400);
});
}/**
* ========================================
* 4. src/accessChecker.js
* (hàm chính – orchestration)
* ========================================
*/
import { validateUser } from "./auth/validateUser.js";
import { fetchUserPermissions } from "./auth/fetchPermissions.js";
import { checkAccess } from "./auth/checkAccess.js";
/**
* Check user access to a resource through promise chain
*
* @param {string} userId
* @param {string} resource
* @returns {Promise<{ granted: boolean, resource: string }>}
*/
export async function checkUserAccess(userId, resource) {
console.log(`\n=== Checking access for user: ${userId} to ${resource} ===`);
try {
const user = await validateUser(userId);
const permissions = await fetchUserPermissions(user.userId);
const accessResult = await checkAccess(permissions, resource);
console.log("Final result: Access granted ✓");
return accessResult;
} catch (error) {
console.error("Access check failed:", error.message);
throw error;
}
}
/**
* Alternative: Promise chain style (nếu không dùng async/await)
*
* @param {string} userId
* @param {string} resource
* @returns {Promise<{ granted: boolean, resource: string }>}
*/
export function checkUserAccessPromise(userId, resource) {
return validateUser(userId)
.then((user) => fetchUserPermissions(user.userId))
.then((permissions) => checkAccess(permissions, resource))
.then((result) => {
console.log("Final result: Access granted ✓");
return result;
})
.catch((error) => {
console.error("Access check failed:", error.message);
throw error;
});
}/**
* ========================================
* 5. src/index.js
* (Re-export – public API)
* ========================================
*/
export { checkUserAccess, checkUserAccessPromise } from "./accessChecker.js";
// Nếu sau này có thêm util, export ở đây
// export * from './utils/validators.js';/**
* ========================================
* Cách sử dụng (ví dụ trong file khác)
* ========================================
*
* // main.js hoặc test file
*/
import { checkUserAccess } from "./src/index.js";
async function test() {
try {
const result = await checkUserAccess("user123", "posts:edit");
console.log("Result:", result);
} catch (err) {
console.log("Error:", err.message);
}
}
test();/**
* ========================================
* Lợi ích của cấu trúc này
* ========================================
*
* - Single Responsibility: Mỗi file chỉ làm 1 việc rõ ràng
* - Dễ test: Có thể test riêng từng hàm (validateUser, fetchPermissions, checkAccess)
* - Dễ mở rộng: Thêm bước mới (ví dụ: logActivity, rateLimitCheck) chỉ cần import thêm
* - Re-export qua index: Caller chỉ cần import từ một nơi duy nhất
* - Tree-shaking friendly: Chỉ bundle những gì thực sự dùng
* - Phù hợp cả ESM & TypeScript (nếu sau này thêm .ts thì chỉ cần thêm type)
*/Bài 3: Safe Data Access
/**
* Viết function extractUserInfo(apiResponse) với:
* - Safe nested access
* - Appropriate defaults
* - Preserve valid falsy values
*/💡 Solution
/**
* Extract thông tin user từ API response một cách an toàn
* Sử dụng optional chaining (?.) và nullish coalescing (??)
* để tránh lỗi khi nested properties không tồn tại
*
* Yêu cầu:
* - Không crash khi data thiếu bất kỳ cấp nào
* - Cung cấp default hợp lý khi giá trị missing
* - Giữ nguyên các falsy values hợp lệ (0, false, '', null)
* - Code clean, dễ đọc, dễ mở rộng
*
* @param {any} apiResponse - API response object (có thể null/undefined)
* @returns {Object} Thông tin user đã được xử lý an toàn
*/
function extractUserInfo(apiResponse) {
// Lấy user một cách an toàn ngay từ đầu
const user = apiResponse?.data?.user;
// Nếu không có user → trả về toàn bộ default
if (!user) {
return getDefaultUserInfo();
}
// Lấy các phần nested an toàn
const profile = user.profile ?? {};
const address = profile.address ?? {};
const preferences = user.preferences ?? {};
const stats = user.stats ?? {};
return {
// Basic info
id: user.id ?? null,
name: user.name ?? "Guest",
email: user.email ?? "no-email@provided.com",
username: user.username ?? "", // giữ '' nếu có
// Profile
avatar: profile.avatar ?? "/default-avatar.png",
bio: profile.bio ?? "",
joinedAt: profile.joinedAt ?? null,
// Address (nếu có)
location:
address.city && address.country
? `${address.city}, ${address.country}`
: "Not specified",
// Preferences – giữ falsy hợp lệ (dark mode = false, notifications = 0)
theme: preferences.theme ?? "light",
notificationsEnabled: preferences.notifications?.enabled ?? true,
emailNotifications: preferences.notifications?.email ?? false,
// Stats – giữ 0 nếu có
stats: {
posts: stats.posts ?? 0,
followers: stats.followers ?? 0,
following: stats.following ?? 0,
reputation: stats.reputation ?? 0,
},
// Flags
isVerified: user.isVerified ?? false,
isAdmin: user.isAdmin ?? false,
hasPremium: user.hasPremium ?? false,
};
}
/**
* Default user info khi không có data user
* Tách riêng để dễ bảo trì
*/
function getDefaultUserInfo() {
return {
id: null,
name: "Guest",
email: "no-email@provided.com",
username: "",
avatar: "/default-avatar.png",
bio: "",
joinedAt: null,
location: "Not specified",
theme: "light",
notificationsEnabled: true,
emailNotifications: false,
stats: {
posts: 0,
followers: 0,
following: 0,
reputation: 0,
},
isVerified: false,
isAdmin: false,
hasPremium: false,
};
}
// ========================================
// Ví dụ sử dụng & test cases
// ========================================
// Case 1: Full data
const fullResponse = {
data: {
user: {
id: 123,
name: "Tuân Lê",
email: "tuan@example.com",
username: "tuan_dev",
profile: {
avatar: "https://example.com/avatar.jpg",
bio: "Full-stack developer",
joinedAt: "2023-05-15",
address: {
city: "Biên Hòa",
country: "Vietnam",
},
},
preferences: {
theme: "dark",
notifications: {
enabled: true,
email: false,
},
},
stats: {
posts: 42,
followers: 1500,
following: 320,
reputation: 0, // falsy hợp lệ
},
isVerified: true,
isAdmin: false,
hasPremium: true,
},
},
};
console.log(extractUserInfo(fullResponse));
// → name: 'Tuân Lê', theme: 'dark', reputation: 0, location: 'Biên Hòa, Vietnam', ...
// Case 2: Thiếu nhiều phần
const partialResponse = {
data: {
user: {
id: 456,
name: "",
email: "user456@gmail.com",
},
},
};
console.log(extractUserInfo(partialResponse));
// → name: 'Guest' (vì name rỗng), avatar: default, theme: 'light', ...
// Case 3: Response lỗi / null
console.log(extractUserInfo(null)); // → full default
console.log(extractUserInfo({})); // → full default
console.log(extractUserInfo({ data: {} })); // → full defaultTại sao cách này tốt?
- An toàn 100%: Không bao giờ throw TypeError do truy cập nested undefined/null
- Giữ falsy hợp lệ:
??chỉ fallback khinull/undefined, không ảnh hưởng0,false,'' - Dễ đọc & bảo trì: Tách default riêng, cấu trúc return rõ ràng theo nhóm
- Dễ mở rộng: Thêm field mới chỉ cần thêm dòng tương ứng
- Production-ready: Xử lý mọi edge case phổ biến trong API response
Nếu API có cấu trúc khác (ví dụ: apiResponse.user thay vì apiResponse.data.user), chỉ cần điều chỉnh dòng const user = apiResponse?.data?.user; là xong.
Nâng cao (60 phút)
Bài 1: Async Data Pipeline
/**
* Xây dựng pipeline xử lý data async:
* 1. Fetch data từ multiple APIs (parallel)
* 2. Combine results
* 3. Transform data
* 4. Cache results
*
* Use: Promise.all, async/await, proper error handling
*/💡 Solution
/**
* Async Data Pipeline - Xử lý dữ liệu bất đồng bộ từ nhiều nguồn
*
* Mô tả pipeline:
* 1. Fetch song song từ nhiều API (sử dụng Promise.all)
* 2. Kết hợp (combine) kết quả từ các nguồn
* 3. Biến đổi (transform) dữ liệu tổng hợp
* 4. Lưu vào cache (in-memory đơn giản) để tái sử dụng
*
* @param {string[]} apiUrls - Mảng các URL API cần gọi
* @param {Object} [options] - Tùy chọn pipeline
* @param {number} [options.cacheTTL=300000] - Thời gian sống của cache (ms) - mặc định 5 phút
* @param {boolean} [options.skipCache=false] - Bỏ qua cache và luôn fetch mới
* @returns {Promise<Object>} Kết quả đã được xử lý và biến đổi
* @throws {AggregateError} Nếu có một hoặc nhiều request thất bại
*
* @example
* const urls = [
* 'https://api.example.com/users',
* 'https://api.example.com/posts',
* 'https://api.example.com/comments'
* ];
*
* const result = await runAsyncDataPipeline(urls, { cacheTTL: 60000 });
* console.log(result.combinedData);
*/
async function runAsyncDataPipeline(apiUrls, options = {}) {
const {
cacheTTL = 5 * 60 * 1000, // 5 phút
skipCache = false,
} = options;
// Cache in-memory đơn giản (có thể thay bằng Redis/Map với TTL thực tế)
/** @type {Map<string, { data: any, timestamp: number }>} */
const cache = new Map();
const cacheKey = apiUrls.sort().join("|"); // key dựa trên danh sách URL đã sắp xếp
// Kiểm tra cache
if (!skipCache && cache.has(cacheKey)) {
const cached = cache.get(cacheKey);
if (Date.now() - cached.timestamp < cacheTTL) {
console.log("Trả về từ cache");
return cached.data;
}
}
try {
// Bước 1: Fetch song song từ tất cả API
const responses = await Promise.all(
apiUrls.map(async (url, index) => {
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`HTTP ${res.status} từ ${url}`);
}
const data = await res.json();
console.log(`API ${index + 1} (${url}) thành công`);
return { url, data, success: true };
} catch (err) {
console.error(`API ${index + 1} (${url}) thất bại:`, err.message);
return { url, error: err.message, success: false };
}
}),
);
// Kiểm tra xem có API nào thất bại không
const failed = responses.filter((r) => !r.success);
if (failed.length > 0) {
throw new AggregateError(
failed.map((f) => new Error(f.error)),
`Có ${failed.length} API thất bại`,
);
}
// Bước 2: Kết hợp kết quả
const combinedData = responses.reduce((acc, { url, data }) => {
const sourceName =
new URL(url).pathname.split("/").pop() ||
"source" + responses.indexOf({ url, data });
acc[sourceName] = data;
return acc;
}, /** @type {Record<string, any>} */ ({}));
// Bước 3: Transform dữ liệu (ví dụ minh họa)
const transformed = {
timestamp: new Date().toISOString(),
totalSources: responses.length,
combined: combinedData,
summary: {
userCount: combinedData.users?.length || 0,
postCount: combinedData.posts?.length || 0,
commentCount: combinedData.comments?.length || 0,
},
// Ví dụ enrich thêm metadata
metadata: {
processedAt: new Date(),
sourceUrls: apiUrls,
},
};
// Bước 4: Lưu vào cache
cache.set(cacheKey, {
data: transformed,
timestamp: Date.now(),
});
console.log("Pipeline hoàn tất - dữ liệu đã được cache");
return transformed;
} catch (error) {
console.error("Pipeline thất bại:", error);
throw error;
}
}
// ========================================
// Ví dụ sử dụng
// ========================================
/**
* @example
* async function demo() {
* const urls = [
* 'https://jsonplaceholder.typicode.com/users',
* 'https://jsonplaceholder.typicode.com/posts',
* 'https://jsonplaceholder.typicode.com/comments'
* ];
*
* try {
* const result = await runAsyncDataPipeline(urls, { cacheTTL: 60000 });
* console.log('Tổng số user:', result.summary.userCount);
* console.log('Tổng số bài viết:', result.summary.postCount);
* } catch (err) {
* console.error('Lỗi pipeline:', err);
* }
* }
*
* demo();
*/Bài 2: Advanced Array Operations
/**
* Implement các functions:
* - groupBy(array, key) - Group array by property
* - partition(array, predicate) - Split into [true, false]
* - unique(array, key) - Remove duplicates
* - sortByMultiple(array, keys) - Sort by multiple fields
*
* Requirements:
* - Use appropriate array methods
* - Optimize performance
* - Handle edge cases
*/💡 Solution
/**
* Nhóm mảng các object theo một key (property) cụ thể.
* Tạo object với key là giá trị của property, value là mảng các phần tử tương ứng.
*
* @template T
* @param {T[]} array - Mảng các object cần nhóm
* @param {string | ((item: T) => any)} key - Tên property hoặc hàm trích xuất key
* @returns {Record<string | number, T[]>} Object nhóm theo key
*
* @example
* const users = [{ id: 1, role: 'admin' }, { id: 2, role: 'user' }, { id: 3, role: 'admin' }];
* groupBy(users, 'role') // → { admin: [{...}, {...}], user: [{...}] }
*/
export function groupBy(array, key) {
if (!Array.isArray(array)) return {};
const getKey = typeof key === "function" ? key : (item) => item?.[key];
return array.reduce((acc, item) => {
const groupKey = getKey(item);
const group = acc[groupKey] || (acc[groupKey] = []);
group.push(item);
return acc;
}, /** @type {Record<string | number, T[]>} */ ({}));
}
/**
* Chia mảng thành hai phần: phần tử thỏa mãn predicate và phần tử không thỏa mãn.
*
* @template T
* @param {T[]} array - Mảng đầu vào
* @param {(item: T) => boolean} predicate - Hàm kiểm tra điều kiện
* @returns {[T[], T[]]} Tuple [phần tử true, phần tử false]
*
* @example
* const numbers = [1, 2, 3, 4, 5];
* partition(numbers, n => n % 2 === 0) // → [[2,4], [1,3,5]]
*/
export function partition(array, predicate) {
if (!Array.isArray(array)) return [[], []];
return array.reduce(
([pass, fail], item) => {
(predicate(item) ? pass : fail).push(item);
return [pass, fail];
},
[[], []],
);
}
/**
* Loại bỏ các phần tử trùng lặp trong mảng, dựa trên key hoặc toàn bộ object.
* Giữ lại phần tử xuất hiện đầu tiên.
*
* @template T
* @param {T[]} array - Mảng đầu vào
* @param {string | ((item: T) => any)} [key] - Property hoặc hàm tạo key duy nhất (nếu không truyền thì so sánh toàn bộ object)
* @returns {T[]} Mảng đã loại bỏ trùng lặp
*
* @example
* unique([{id:1,name:'A'}, {id:1,name:'B'}, {id:2,name:'C'}], 'id')
* // → [{id:1,name:'A'}, {id:2,name:'C'}]
*/
export function unique(array, key) {
if (!Array.isArray(array)) return [];
if (!key) {
// So sánh toàn bộ object (dùng Set + JSON)
const seen = new Set();
return array.filter((item) => {
const str = JSON.stringify(item);
if (seen.has(str)) return false;
seen.add(str);
return true;
});
}
const getKey = typeof key === "function" ? key : (item) => item?.[key];
const seen = new Map();
return array.filter((item) => {
const k = getKey(item);
if (seen.has(k)) return false;
seen.set(k, true);
return true;
});
}
/**
* Sắp xếp mảng theo nhiều tiêu chí (multi-key sort).
* Các key được ưu tiên từ trái sang phải.
*
* @template T
* @param {T[]} array - Mảng cần sắp xếp
* @param {Array<
* string | // sort theo field đơn giản
* { key: string, order?: 'asc'|'desc' } // sort theo field + hướng
* | ((a:T,b:T)=>number) // custom comparator
* >} keys
* @returns {T[]} Mảng mới đã được sắp xếp (không mutate mảng gốc)
*/
export function sortByMultiple(array, keys) {
// Defensive: nếu input không phải array thì trả về mảng rỗng
if (!Array.isArray(array)) return [];
// Clone mảng trước khi sort để tránh mutate dữ liệu gốc
return [...array].sort((a, b) => {
// Duyệt từng tiêu chí theo thứ tự ưu tiên
for (const criterion of keys) {
// key: field dùng để sort
// direction: 1 = asc, -1 = desc
let key,
direction = 1;
/**
* CASE 1: criterion là function
* → dùng như comparator custom
* → giống native Array.sort(compareFn)
*/
if (typeof criterion === "function") {
const result = criterion(a, b);
// Nếu đã phân thắng bại thì return luôn
if (result !== 0) return result;
// Nếu bằng nhau thì xét tiêu chí tiếp theo
continue;
}
/**
* CASE 2: criterion là object { key, order }
*/
if (typeof criterion === "object" && criterion !== null) {
key = criterion.key;
// Nếu order là 'desc' thì đảo chiều
direction = criterion.order === "desc" ? -1 : 1;
} else {
/**
* CASE 3: criterion là string
* → coi như key, mặc định asc
*/
key = criterion;
}
// Lấy giá trị cần so sánh (optional chaining để tránh crash)
const valA = a?.[key];
const valB = b?.[key];
// Nếu hai giá trị bằng nhau → xét tiêu chí tiếp theo
if (valA === valB) continue;
/**
* Xử lý null / undefined:
* - luôn đẩy null / undefined xuống cuối
*/
if (valA == null) return 1;
if (valB == null) return -1;
/**
* Nếu là string → dùng localeCompare
* (an toàn với Unicode, tiếng Việt, đa ngôn ngữ)
*/
if (typeof valA === "string" && typeof valB === "string") {
const cmp = valA.localeCompare(valB);
// Nhân với direction để xử lý asc / desc
if (cmp !== 0) return cmp * direction;
} else {
/**
* Các kiểu còn lại: number, date, boolean
*/
const cmp = valA < valB ? -1 : 1;
// Nhân direction để đảo chiều nếu cần
if (cmp !== 0) return cmp * direction;
}
}
// Nếu tất cả tiêu chí đều bằng nhau
return 0;
});
}📚 TÀI LIỆU THAM KHẢO
Bắt buộc đọc
MDN: Promises
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- Focus: Promise basics, then, catch, finally
MDN: async/await
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- Focus: Syntax, error handling, common patterns
MDN: ES6 Modules
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
- Focus: import/export syntax, best practices
MDN: Optional Chaining
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
- Focus: Syntax variants, use cases
MDN: Nullish Coalescing
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing
- Focus: Difference from ||, when to use
Đọc thêm
JavaScript.info: Promises
- https://javascript.info/promise-basics
- Excellent visual explanations
ES6 Modules Deep Dive
- https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/
- How modules work under the hood
Array Method Performance
- https://www.measurethat.net/
- Benchmark different approaches
🔗 KẾT NỐI KIẾN THỨC
Kiến thức nền (từ Ngày 1)
- Destructuring → Dùng trong các câu lệnh import
- Spread operator → Dùng trong các phép biến đổi mảng
- Arrow functions → Dùng làm callback cho array methods
- Template literals → Dùng trong các tiện ích xử lý chuỗi
Hướng tới (Ngày tiếp theo)
Ngày 3: React Cơ Bản & JSX
- Sẽ dùng: ES6 modules để import React
- Sẽ dùng: Arrow functions để viết components
- Sẽ dùng: Destructuring cho props
- Sẽ dùng: Array methods để render danh sách
Ngày 19: Lấy Dữ Liệu (Data Fetching)
- Sẽ THỰC SỰ sử dụng: async/await
- Sẽ THỰC SỰ sử dụng: Promise.all
- Sẽ dùng: Optional chaining cho dữ liệu trả về từ API
💡 GÓC NHÌN SENIOR
Cân Nhắc Khi Đưa Vào Production
1. Async Patterns trong React
// ⚠️ HÔM NAY học khái niệm, NGÀY 19 mới áp dụng thực tế
// React components CẦN những pattern đặc biệt cho async:
// - useEffect để fetch dữ liệu
// - Loading states
// - Error boundaries
// Hiện tại chỉ cần hiểu: async/await HOẠT ĐỘNG như thế nào2. Đóng Gói Module (Module Bundling)
// Production build sẽ bundle các modules lại
// Tree-shaking sẽ loại bỏ những export không dùng
// Named exports → Tree-shaking hiệu quả hơn
// Luôn cân nhắc ảnh hưởng tới bundle size3. Cân Nhắc Hiệu Năng
// Optional chaining có overhead rất nhỏ
// Không đáng kể trong hầu hết trường hợp
// NHƯNG: Tránh dùng trong hot loops (hàng nghìn lần lặp)
// Luôn profile trước khi tối ưu!🎯 CÂU HỎI PHỎNG VẤN
Junior Level
Q: Sự khác nhau giữa Promise và async/await?
A: async/await là cú pháp sugar giúp code Promise dễ đọc hơn.
async function luôn trả về Promise.
await tạm dừng execution cho đến khi Promise resolve.
Q: Khi nào dùng ?? thay vì ||?
A: Dùng ?? khi muốn giữ lại các falsy value hợp lệ như 0, false, ''.
|| sẽ thay thế TẤT CẢ falsy values.
Q: find() và filter() khác nhau thế nào?
A: find() trả về phần tử đầu tiên thoả điều kiện và dừng sớm.
filter() trả về tất cả phần tử thoả điều kiện và duyệt toàn bộ mảng.Mid Level
Q: Promise.all vs Promise.race vs Promise.allSettled?
A:
- Promise.all: Chờ TẤT CẢ, chỉ cần 1 cái fail → reject
- Promise.race: Trả về promise settle đầu tiên (resolve hoặc reject)
- Promise.allSettled: Chờ TẤT CẢ, không bao giờ reject
Q: Làm sao tối ưu hiệu năng khi xử lý mảng lớn?
A:
- Dùng đúng method (find thay vì filter nếu chỉ cần 1 phần tử)
- Gộp các bước xử lý để giảm số lần lặp
- Cân nhắc lazy evaluation
- Luôn đo (profile) trước khi tối ưu
Q: Circular dependency trong modules, xử lý thế nào?
A:
- Refactor lại cấu trúc code để tránh
- Tách phần dùng chung sang module riêng
- Dùng dependency injection
- Xem lại thiết kế tổng thể, có thể cần refactorSenior Level
Q: Trade-off giữa các module system (ESM vs CommonJS)?
A:
ESM:
- Phân tích tĩnh → Tree-shaking tốt hơn
- Hỗ trợ load bất đồng bộ
- Native trong trình duyệt
- Hỗ trợ top-level await
CommonJS:
- require() động
- Load đồng bộ
- Truyền thống trong Node.js
- Linh hoạt hơn nhưng khó tối ưu hơn
Q: Chiến lược xử lý lỗi cho async trong production?
A:
- try/catch trong async functions
- Error boundaries trong React
- Global error handlers
- Logging / monitoring (Sentry)
- Graceful degradation
- Retry logic cho lỗi tạm thời
- Circuit breaker để tránh lỗi dây chuyền
Q: Khi nào nên dùng reduce() thay vì map/filter chain?
A:
reduce():
- Duyệt mảng 1 lần
- Xử lý aggregation phức tạp
- Tạo nhiều output
- NHƯNG: Khó đọc hơn
map/filter:
- Ý định rõ ràng
- Dễ debug
- Duyệt nhiều lần (có thể chậm hơn)
- NHƯNG: Dễ bảo trì hơn
=> Code quan trọng hiệu năng: đo trước
=> Code thông thường: ưu tiên dễ đọc🧠 WAR STORIES (KINH NGHIỆM THỰC TẾ)
Story 1: Bug Với Nullish Coalescing
Vấn đề: Website thương mại điện tử bị lỗi hiển thị
Root cause: const price = product.price || 'N/A';
Impact: Sản phẩm miễn phí (price = 0)
bị hiển thị là 'N/A'
Fix: const price = product.price ?? 'N/A';
Bài học: LUÔN xem 0, false, '' có phải là giá trị hợp lệ không!Story 2: Thảm Hoạ Promise.all
Vấn đề: Dashboard gọi 10 API bằng Promise.all
Chỉ cần 1 API fail → toàn bộ dashboard trắng xoá
Fix:
- Chuyển sang Promise.allSettled
- Xử lý lỗi từng phần
Bài học: Promise.all là all-or-nothing.
Luôn cân nhắc đúng use case!Story 3: Hiệu Năng Import Module
Vấn đề: Trang load lần đầu rất chậm
Root cause:
import * as Utils from './utils'
→ Import 50KB+ code không dùng
Fix:
import { formatDate } from './utils'
Impact: Giảm bundle size ~40KB
Bài học: Tree-shaking hiệu quả nhất với named imports!🎯 PREVIEW NGÀY MAI
Ngày 3: React Basics & JSX
Bạn sẽ học:
- ✨ Tạo React app đầu tiên
- 🎨 JSX syntax và rules
- 🔄 JSX vs HTML differences
- 💻 JavaScript expressions trong JSX
Chuẩn bị:
- [ ] Ôn lại destructuring (sẽ dùng nhiều!)
- [ ] Ôn lại arrow functions
- [ ] Ôn lại template literals
- [ ] Hoàn thành bài tập về nhà
Hẹn gặp bạn ngày mai! 🚀
📊 Tổng kết Ngày 2:
- ✅ Đã học: Promises, async/await, ES6 Modules, ?., ??, Array methods nâng cao
- ✅ Đã thực hành: 5 exercises từ basic đến production
- ✅ Debug: 3 common bugs
- 🎯 Sẵn sàng: Bắt đầu React journey!