Sửa lỗi: TypeScript 'Top-level await expressions are only allowed'

intermediate🔵 TypeScript2026-07-08| TypeScript 3.8+, Node.js 14.8+, mọi hệ điều hành (Windows, macOS, Linux)

Error Message

Top-level await expressions are only allowed when the 'module' option is set to 'es2022', 'esnext', 'system', 'node16', or 'nodenext'.
#typescript#node.js#tsconfig#esm

Vấn đề

Có lẽ bạn đã gặp khó khăn khi cố gắng lấy dữ liệu hoặc khởi tạo kết nối cơ sở dữ liệu trực tiếp tại gốc của tệp. Thay vì thực thi mượt mà, TypeScript chặn bạn bằng một lỗi trình biên dịch vì nó không nhận diện tệp của bạn là một module hiện đại.

Hãy xem xét tình huống phổ biến sau:

// index.ts
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);

Theo mặc định, TypeScript thường nhắm mục tiêu đến CommonJS. Hệ thống cũ này dựa trên require() và hoàn toàn đồng bộ. Vì await ở cấp cao nhất (top-level await) là một tính năng dành riêng cho ECMAScript Modules (ESM), trình biên dịch sẽ báo lỗi để ngăn mã của bạn bị lỗi khi chạy (runtime).

Tóm tắt: Cách khắc phục nhanh

Mở tsconfig.json và cập nhật các thiết lập của bạn để sử dụng các tiêu chuẩn hiện đại. Ba dòng này thường giải quyết được 90% các trường hợp:

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "nodenext"
  }
}

Nếu bạn đang xây dựng cho Node.js, bạn cũng phải thêm "type": "module" vào tệp package.json của mình.

Các phương pháp khắc phục chi tiết

1. Hiện đại hóa tsconfig.json của bạn

Thông báo lỗi thực sự khá hữu ích. Nó liệt kê các hệ thống module cụ thể hỗ trợ tính năng này. Để khắc phục điều này một cách chính xác, hãy đảm bảo cấu hình của bạn thông báo cho TypeScript rằng môi trường đủ hiện đại để xử lý ESM.

{
  "compilerOptions": {
    /* ES2022 là mục tiêu tối thiểu để hỗ trợ native top-level await */
    "target": "es2022", 
    
    /* Sử dụng 'nodenext' nếu bạn đang dùng Node.js 16 hoặc mới hơn */
    "module": "nodenext", 
    
    "moduleResolution": "nodenext",
    
    /* Bao gồm các định nghĩa API hiện đại trong IDE của bạn */
    "lib": ["esnext", "dom"]
  }
}

Sử dụng nodenext là lựa chọn an toàn nhất cho các dự án backend hiện đại. Nó buộc TypeScript phải tuân theo các quy tắc tương tự như Node.js, chẳng hạn như yêu cầu phần mở rộng tệp trong các câu lệnh import.

2. Bật ESM trong package.json

Cập nhật TypeScript không phải lúc nào cũng đủ. Nếu bạn chạy các tệp .js đã biên dịch trong Node.js, môi trường runtime cần biết rằng nó nên xử lý chúng dưới dạng module thay vì các script kiểu cũ. Thêm trường type vào tệp package.json của bạn:

{
  "name": "my-app",
  "version": "1.0.0",
  "type": "module"
}

Nếu không có điều này, Node.js sẽ ném ra lỗi SyntaxError: Unexpected reserved word khi gặp từ khóa await, ngay cả khi trình biên dịch TypeScript không báo lỗi.

3. Giải pháp thay thế IIFE (Dành cho các dự án cũ)

Đôi khi bạn không thể chuyển sang ESM. Bạn có thể bị kẹt với một codebase cũ hoặc các thư viện phụ thuộc bị lỗi theo các quy tắc module hiện đại. Trong những trường hợp này, hãy bọc logic của bạn trong một Immediately Invoked Function Expression (IIFE).

(async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error("Initialization failed", err);
  }
})();

Điều này đặt await bên trong một khối hàm async. Cách này làm hài lòng trình biên dịch trong khi vẫn cho phép mã của bạn chạy ngay lập tức khi khởi động.

Tại sao điều này xảy ra

JavaScript ban đầu được thiết kế để tải các tệp nối tiếp nhau theo một trình tự đơn giản. Khi async/await xuất hiện trong ES2017, nó bị giới hạn trong thân hàm để giữ cho mọi thứ có thể dự đoán được. Khi hệ sinh thái chuyển sang ECMAScript Modules, đặc tả đã phát triển. Top-level await chính thức được thêm vào trong ES2022. Tuy nhiên, CommonJS—hệ thống mà Node.js đã sử dụng trong hơn một thập kỷ—về cơ bản là đồng bộ. Nó không thể tạm dừng thực thi để chờ một Promise ở cấp cao nhất của một tệp.

Cách xác minh bản sửa lỗi

Đừng chỉ tin vào việc không còn các đường kẻ răng cưa màu đỏ. Hãy làm theo các bước sau để đảm bảo mọi thứ được cấu hình chính xác:

  • Làm mới TS Server: Trong VS Code, mở Command Palette (Cmd/Ctrl + Shift + P) và chạy "TypeScript: Restart TS server".
  • Kiểm tra bản Build: Chạy npx tsc. Lệnh sẽ kết thúc mà không xuất hiện bất kỳ lỗi nào trên terminal của bạn.
  • Kiểm tra đầu ra: Mở tệp dist/index.js của bạn. Bạn sẽ thấy từ khóa await chính xác ở nơi bạn đã viết, thay vì một đống hỗn độn các hàm hỗ trợ __awaiter được chuyển đổi.
  • Kiểm tra Runtime: Chạy node dist/index.js. Nếu dữ liệu của bạn được log ra console, bạn đã thành công.

Các sai lầm thường gặp

  • Node.js lỗi thời: Top-level await yêu cầu Node.js 14.8.0 trở lên. Nếu bạn đang sử dụng phiên bản LTS cũ hơn như Node 12, không có cách khắc phục nào ở trên sẽ hoạt động.
  • Cái bẫy 'target': Nếu target của bạn được đặt thành es5, TypeScript sẽ cố gắng biến mã của bạn thành JavaScript của 20 năm trước. Điều này sẽ gây ra lỗi vì ES5 không có khái niệm về Promises.

Related Error Notes