Menu

fs.readFile trong Node.js: đọc tệp sync, async, promise

Đọc tệp trong Node.js bằng fs.readFile (callback), fs.readFileSync hoặc await readFile từ fs/promises. Truyền 'utf8' để nhận chuỗi, và xử lý ENOENT khi tệp không tồn tại.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

fs.readFile(path, 'utf8', callback) đọc toàn bộ một tệp và truyền nội dung của nó cho callback dưới dạng chuỗi. fs.readFileSync(path, 'utf8') trả về chuỗi trực tiếp và chờ đến khi đọc xong, còn await readFile(path, 'utf8') từ fs/promises làm điều tương tự mà không chặn chương trình. Bỏ 'utf8' đi thì bạn nhận được một Buffer chứa byte thô thay vì văn bản.

readFile called được in ra trước: readFile chỉ bắt đầu việc đọc, còn callback chạy khi nội dung đã sẵn sàng. Xóa 'utf8', rồi chạy lại: callback nhận <Buffer 42 75 79 20 6d 69 6c 6b 0a 43 61 6c 6c 20 41 64 61>, tức các byte thô của tệp.

readFile, readFileSync và fs/promises

Cả ba đều đọc cùng một tệp. Chúng khác nhau ở cách bạn nhận kết quả và việc phần còn lại của chương trình có phải chờ hay không.

Dòng sync được in ra trước vì không có gì khác chạy cho đến khi nó trả về. Hai cách còn lại xong sau phần code chính, theo thứ tự mà các lần đọc hoàn tất.

readFileSyncreadFile (callback)readFile của fs/promises
Trả vềNội dung tệpundefinedPromise chứa nội dung
Chặn chương trìnhCóKhôngKhông
LỗiBị ném ra, dùng try/catchĐối số đầu tiên của callbackPromise bị reject, try/catch với await
Dùng trongCode khởi động và scriptCode callback cũCode mới

Dùng readFileSync khi đọc cấu hình lúc khởi động hoặc trong một script ngắn. Trên server, một lần đọc sync sẽ chặn mọi request khác trong lúc ổ đĩa làm việc, nên hãy dùng bản promise ở đó. Trang fs/promises trình bày phần còn lại của API này.

Xử lý tệp không tồn tại (ENOENT)

Khi tệp không tồn tại, lỗi có code: 'ENOENT' ("error, no entry"). Kiểm tra err.code để phân biệt tệp bị thiếu với các lỗi khác, như lỗi quyền truy cập.

Lần gọi đầu không tìm thấy tệp nên dùng giá trị dự phòng default settings; lần thứ hai đọc được dark mode. Ném lại các lỗi khác giúp những vấn đề thật vẫn hiện ra, thay vì lặng lẽ dùng giá trị mặc định.

fs.existsSync: vì sao nên đọc luôn

fs.existsSync(path) trả về true hoặc false, và bạn dễ muốn kiểm tra trước khi đọc:

if (fs.existsSync('data.txt')) {
    const text = fs.readFileSync('data.txt', 'utf8');
}

Như vậy là hai bước trong khi một bước là đủ, và tệp có thể bị xóa giữa hai bước, nên lệnh đọc vẫn có thể ném lỗi. Hãy đọc luôn và xử lý ENOENT như ở trên. existsSync vẫn ổn khi bạn chỉ cần biết thứ gì đó có tồn tại hay không mà không mở nó, ví dụ để quyết định có tạo thư mục hay không.

Đọc tệp JSON

Đọc văn bản, rồi parse bằng JSON.parse. Tệp có lỗi cú pháp sẽ ném SyntaxError, nên hãy đặt nó trong cùng khối try.

Chương trình in ra 3001 và boolean: sau khi parse, các giá trị mang đúng kiểu JSON của chúng. require('./config.json') cũng dùng được, nhưng nó cache kết quả, nên lần require thứ hai vẫn trả về object cũ dù tệp đã thay đổi.

Đọc tệp từng dòng

Với tệp vừa bộ nhớ, hãy đọc toàn bộ rồi tách theo ký tự xuống dòng. Lọc bỏ chuỗi rỗng do ký tự xuống dòng cuối tệp để lại.

Phép tách cho ra 4 phần tử, phần tử cuối rỗng, nên vòng lặp in ra ba dòng có đánh số. Tệp viết trên Windows kết thúc dòng bằng \r\n; hãy tách theo /\r?\n/ để xử lý cả hai.

Một tệp log vài gigabyte không vừa bộ nhớ. Hãy đọc nó theo luồng (stream), để mỗi lúc chỉ giữ một dòng:

const fs = require('fs');
const readline = require('readline');

async function main() {
    const rl = readline.createInterface({
        input: fs.createReadStream('big.log'),
        crlfDelay: Infinity,
    });
    for await (const line of rl) {
        console.log(line);
    }
}
main();

Lỗi thường gặp

Dùng kết quả bên ngoài callback. const text = fs.readFile(...) là undefined. Nội dung tệp chỉ có bên trong callback, hoặc sau await.

Quên encoding. Thiếu 'utf8' thì bạn nhận một Buffer, và data.split('\n') ném lỗi data.split is not a function. Hãy truyền encoding mỗi khi bạn muốn nhận văn bản.

Đường dẫn tương đối tính từ sai thư mục. 'data.txt' được tìm ở nơi bạn khởi chạy node. Dùng path.join(__dirname, 'data.txt') cho tệp đi kèm cạnh script.

Câu hỏi thường gặp

Vì sao fs.readFile trả về undefined?

fs.readFile không trả về nội dung tệp. Nó bắt đầu việc đọc và gọi callback của bạn sau đó với (err, data). Hãy dùng dữ liệu bên trong callback, gọi readFileSync, hoặc await bản promise.

Node tìm đường dẫn tương đối như 'data.txt' ở đâu?

Trong thư mục làm việc hiện tại, tức thư mục bạn chạy lệnh node, không phải thư mục chứa script. Để đọc tệp nằm cạnh script, hãy ghép đường dẫn bằng path.join(__dirname, 'data.txt').

fs.readFile có đọc được tệp từ URL không?

Không, nó chỉ đọc từ hệ thống tệp. Để lấy tệp qua HTTP, hãy dùng fetch rồi await res.text().

readFile xử lý được tệp lớn đến mức nào?

Nó nạp toàn bộ tệp vào bộ nhớ, nên phù hợp với tệp cấu hình, template và tệp dữ liệu nhỏ. Với tệp hàng trăm megabyte, hãy đọc từng phần bằng fs.createReadStream.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu