JavaScript ES2025+: những pattern thực dụng dev web đang dùng để viết code dễ bảo trì hơn

3 tháng trước · 7 phút đọc
Tại sao pattern quan trọng hơn cú pháp mới
Mỗi năm TC39 ra spec mới, dev lại đổ xô đọc changelog. Optional chaining, nullish coalescing, top-level await... học xong rồi sao? Nhiều người vẫn viết code y hệt cũ, chỉ là thay && bằng ?..
Vấn đề thật ra không nằm ở cú pháp. Nằm ở cách tổ chức tư duy khi code. Pattern là tư duy đó được đúc kết thành template có thể tái sử dụng.
Bài này mình sẽ không liệt kê feature mới của ES2025. Thay vào đó, mình sẽ chỉ những pattern mà mình và nhiều dev đang dùng thực tế - những thứ giúp pull request dễ review hơn, bug dễ tìm hơn, và người vào sau dự án không muốn bỏ việc.
Pattern tốt = ít thời gian debug, nhiều thời gian build
Nếu bạn chưa vững nền tảng JS, khóa Lập Trình JavaScript Nâng Cao của F8 cover khá kỹ về closure, reference types, và các cơ chế bên dưới - những thứ cần hiểu trước khi áp dụng pattern.
Module organization: thoát khỏi file 800 dòng
Bạn đang review PR. Thấy 1 file utils.js dài 600 dòng với đủ loại hàm: format date, validate form, gọi API, xử lý auth. Đây là dấu hiệu dự án chưa có chiến lược module.
Pattern mình đang dùng là feature-first modules kết hợp barrel exports:
Lợi ích thực tế: khi refactor nội bộ module cart, các file bên ngoài không cần đổi import. Chỉ cần giữ contract ở index.js ổn định.
Barrel export giữ API ổn định dù nội bộ thay đổi
Kết hợp với ES2025 import attributes (đang ở stage 3-4), bạn còn có thể làm rõ loại module đang import:
Mình thấy pattern này đặc biệt hữu ích khi team có cả dev mới. Người mới chỉ cần đọc index.js của từng feature là hiểu public API, không cần đào sâu vào implementation.
Immutable data handling: sống sót qua state phức tạp
Bug khó chịu nhất trong JS là mutation bất ngờ. Bạn truyền object vào function, function xử lý xong, nhìn lại object gốc đã thay đổi. Trace bug mất 2 tiếng mới phát hiện ra.
Với nested object sâu hơn, spread operator bắt đầu trở nên verbose. Đây là lúc structuredClone() (có sẵn từ Node 17+, browser hiện đại) phát huy tác dụng:
structuredClone() thay thế lodash cloneDeep trong nhiều trường hợp
ES2025 còn có Array.fromAsync() và cải tiến cho Set (union, intersection, difference) giúp xử lý collection immutably gọn hơn:
Trade-off cần nhớ: immutable pattern tạo nhiều object mới hơn, tức là garbage collector làm việc nhiều hơn. Với array vài nghìn phần tử xử lý liên tục, đây là điểm cần cân nhắc. Nhưng với 90% use case trong web app thông thường, lợi ích về predictability hoàn toàn xứng đáng.
Async patterns: vượt qua callback hell và promise chain dài
Async/await đã giải quyết callback hell. Nhưng viết async không cẩn thận vẫn dễ tạo ra những vấn đề khác: sequential await khi có thể parallel, unhandled rejection, race condition.
Pattern 1: Parallel awaiting thay vì sequential
Promise.all giảm load time từ 750ms xuống còn 300ms
Pattern 2: Error handling rõ ràng với Result type
Thay vì try/catch dày đặc hoặc để lọt unhandled rejection, một số team đang dùng pattern Result type lấy cảm hứng từ Rust/Haskell:
Pattern 3: ES2025 Promise.try()
ES2025 giới thiệu Promise.try() - cách gọi function sync hoặc async mà không cần biết nó là loại nào:
Mình thấy Promise.try() hữu ích nhất khi viết middleware hoặc plugin system - nơi bạn không kiểm soát được user truyền vào sync hay async function.
Kết hợp với framework hiện đại: pattern không chỉ là vanilla JS
Các pattern trên không sống tách biệt. Chúng phát huy sức mạnh khi kết hợp với cách framework hiện đại được thiết kế.
React + immutable pattern: React Server Components (RSC) và useState đều mong đợi state là immutable. Nếu bạn mutate state trực tiếp, React không detect được sự thay đổi và không re-render. Immutable pattern trở thành bắt buộc, không còn là lựa chọn.
Module organization + dynamic import: Barrel exports kết hợp với lazy loading giúp bundle size giảm đáng kể:
Lazy load feature module giúp initial bundle nhẹ hơn
Async patterns + data fetching: Với React Query hoặc SWR, bạn ít khi cần viết async logic thủ công. Nhưng safeAsync wrapper và Promise.all vẫn hữu ích ở tầng service layer - nơi bạn orchestrate nhiều API call trước khi trả về cho hook.
Nếu bạn muốn đi sâu vào React và các pattern này trong môi trường framework thực tế, khóa Xây Dựng Website với ReactJS của F8 có phần riêng về state management và async data fetching khá chi tiết.
Takeaways:
- Barrel exports giữ module API ổn định khi refactor nội bộ
- Immutable data loại bỏ class bug khó trace nhất trong JS
- Promise.all + safeAsync vừa nhanh vừa không để lọt unhandled error
- Các pattern này không mâu thuẫn với framework - chúng là nền tảng framework được xây dựng lên
Thử áp dụng 1 pattern vào project hiện tại trước. Không cần refactor cả codebase. Bắt đầu từ file tiếp theo bạn tạo mới.
