跪拜 Guibai
← All articles
Backend · JavaScript

Node.js File Reading: From Callback Hell to Clean Async/Await

By mONESY ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Picking the wrong read pattern in a Node service directly caps throughput: a single `readFileSync` in a request handler freezes the event loop for every concurrent user. The async/await + `fs/promises` combo is the only pattern that keeps code linear without sacrificing concurrency.

Summary

The `fs` module's evolution mirrors JavaScript's own asynchronous journey. `readFileSync` blocks the entire thread and belongs only in startup config loads. The original error-first callback API works but collapses into deeply nested callback hell when reading files in series. Switching to `fs/promises` and `.then()` chains flattens the nesting, though repetitive boilerplate remains. The production-ready pattern is `fs/promises` with async/await wrapped in an IIFE, producing linear, readable code that stays non-blocking. Every read should sit inside a try/catch block, and paths should be resolved to absolute form with `path.resolve` to survive working-directory changes.

Takeaways
`readFileSync` blocks the entire thread and should only be used for one-time config loads at startup, never inside request handlers.
Callback-based `readFile` follows an error-first convention but produces deeply nested code when reading multiple files serially.
Promise chaining with `fs/promises` eliminates nesting but still requires repetitive `.then()` calls for each file.
async/await with `fs/promises` keeps code linear and non-blocking; wrap it in an IIFE because top-level await is not allowed in older Node or CommonJS.
Every file read in production needs a try/catch block, and paths should be built with `path.resolve` to avoid working-directory bugs.
Conclusions

Node's file I/O API history is a compressed replay of JavaScript's larger async story — blocking, callbacks, Promises, then async/await — all coexisting in one module.

The `fs/promises` import is a cleaner entry point than mixing callback and Promise styles from the base `fs` module, yet many older codebases still import the wrong one.

Concepts & terms
Error-first callback
A Node.js convention where a callback's first argument is an error object (or null on success) and the second is the result data, standardizing async error handling before Promises.
IIFE (Immediately Invoked Function Expression)
A function defined and called at the same time, used to create a scope where `await` can run when top-level await isn't available.
Microtask vs Macrotask
In the JavaScript event loop, microtasks (Promise callbacks, `await` continuations) run before the next macrotask (setTimeout, I/O callbacks), giving Promises higher scheduling priority.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗