Skip to content

📅 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:

  1. Destructuring: const { name, age } = user - Bạn có hiểu cách này hoạt động không?
  2. Spread operator: const newArr = [...oldArr, newItem] - Tại sao cần dấu ...?
  3. Arrow functions: const double = x => x * 2 - Khác gì với function 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"

javascript
// ❌ 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

javascript
// ❌ 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

javascript
// ❌ 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

javascript
// ========================================
// 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:

PatternReadabilityError HandlingDebugging
Callbacks
Promises⭐⭐⭐⭐⭐⭐⭐⭐⭐
async/await⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

B. async/await Best Practices

javascript
// ========================================
// 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

javascript
// ========================================
// 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

javascript
// ========================================
// 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 (?.)

javascript
// ========================================
// 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 (??)

javascript
// ========================================
// 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

javascript
// ========================================
// 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 ⭐⭐⭐

javascript
// ========================================
// 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:

MethodReturnsStops Early?Use when
find()Element hoặc undefined✅ YesTìm 1 element
findIndex()Index hoặc -1✅ YesTìm vị trí element
some()Boolean✅ YesCheck ÍT NHẤT 1 thỏa điều kiện
every()Boolean✅ YesCheck TẤT CẢ thỏa điều kiện
filter()Array❌ NoLấy TẤT CẢ elements thỏa điều kiện
map()Array❌ NoTransform 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)

javascript
/**
 * 🎯 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)

javascript
/**
 * 🎯 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

javascript
// 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

javascript
// 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

javascript
// 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)

javascript
// utils/index.js

export * from "./string.js";
export * from "./number.js";
export * from "./date.js";

📁 src/app.js (sử dụng)

javascript
// 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)

javascript
/**
 * 🎯 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)

javascript
/**
 * 🎯 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
javascript
/**
 *
 * =====================================================
 * 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);
 *
 */
javascript
// ========================================
// 💻 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
javascript
/**
 * ========================================
 * 💻 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());
 */
javascript
// ========================================
// 🧪 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)

javascript
/**
 * 🎯 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)
javascript
/**
 * 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

AspectCallbacksPromisesasync/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 CaseLegacy codeModern async codePreferred 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

AspectDefault ExportNamed Export
Naming Freedom✅ Import with any name❌ Must use exact name (or alias)
Import Syntaximport 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 CaseMain export (component, class)Utilities, constants

Best Practices:

javascript
// ✅ 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

AspectTraditionalOptional Chaining
Code Length❌ Very verbose✅ Concise
Readability⭐⭐⭐⭐⭐⭐⭐
Performance⭐⭐⭐⭐⭐ (Slightly faster)⭐⭐⭐⭐ (Negligible difference)
Browser Support⭐⭐⭐⭐⭐ (All)⭐⭐⭐⭐ (ES2020+)
Maintenance❌ Error-prone✅ Safer
javascript
// 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 elementfilter()[0]find()
Check existencefilter().length > 0some()
Validate allfilter().length === arr.lengthevery()
Multiple operationsChain multiple loopsCombine in 1 loop

🧪 PHẦN 5: DEBUG LAB (20 phút)

Bug 1: Promise Not Awaited

javascript
// 🐛 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 await

Bug 2: OR vs Nullish Coalescing

javascript
// 🐛 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 case

Bug 3: Optional Chaining with Function Calls

javascript
// 🐛 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

markdown
- [ ] 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 issues

Code Review Checklist

markdown
#### 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

javascript
/**
 * 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
javascript
/**
 * 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

javascript
/**
 * 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
javascript
/**
 * ========================================
 * 📁 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
 */
javascript
/**
 * ========================================
 * 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);
  });
}
javascript
/**
 * ========================================
 * 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);
  });
}
javascript
/**
 * ========================================
 * 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);
  });
}
javascript
/**
 * ========================================
 * 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;
    });
}
javascript
/**
 * ========================================
 * 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';
javascript
/**
 * ========================================
 * 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();
javascript
/**
 * ========================================
 * 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

javascript
/**
 * Viết function extractUserInfo(apiResponse) với:
 * - Safe nested access
 * - Appropriate defaults
 * - Preserve valid falsy values
 */
💡 Solution
javascript
/**
 * 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 default

Tạ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 khi null/undefined, không ảnh hưởng 0, 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

javascript
/**
 * 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
javascript
/**
 * 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

javascript
/**
 * 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
javascript
/**
 * 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

  1. MDN: Promises

  2. MDN: async/await

  3. MDN: ES6 Modules

  4. MDN: Optional Chaining

  5. MDN: Nullish Coalescing

Đọc thêm

  1. JavaScript.info: Promises

  2. ES6 Modules Deep Dive

  3. Array Method Performance


🔗 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

javascript
// ⚠️ 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ào

2. Đóng Gói Module (Module Bundling)

javascript
// 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 size

3. Cân Nhắc Hiệu Năng

javascript
// 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 refactor

Senior 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!

Personal tech knowledge base