File System
fs module สามรูปแบบ
หัวข้อที่มีชื่อว่า “fs module สามรูปแบบ”Node มาพร้อม filesystem operations เดียวกันใน API สามแบบที่แตกต่างกัน:
| รูปแบบ | Import | ใช้เมื่อไหร่ |
|---|---|---|
| Callback (async) | import fs from 'node:fs' | code เก่า, Node streams |
| Synchronous | import fs from 'node:fs' | CLI scripts, config ตอน startup |
| Promise (async) | import fs from 'node:fs/promises' | server code ทั้งหมดที่เขียนใหม่ |
การอ่านไฟล์
หัวข้อที่มีชื่อว่า “การอ่านไฟล์”แบบ Callback
หัวข้อที่มีชื่อว่า “แบบ Callback”import fs from 'node:fs';
fs.writeFileSync('./hello.txt', 'Hello from Node.js!');
fs.readFile('./hello.txt', 'utf8', function(err, data) { if (err) throw err; console.log(data); fs.unlinkSync('./hello.txt');});แบบ Promise (แนะนำ)
หัวข้อที่มีชื่อว่า “แบบ Promise (แนะนำ)”Needs the Node.js runtime — open in StackBlitz to run.
การเขียนไฟล์
หัวข้อที่มีชื่อว่า “การเขียนไฟล์”writeFile สร้างไฟล์ถ้ายังไม่มี และ แทนที่ เนื้อหาถ้ามีอยู่แล้ว ใช้ appendFile เพื่อเพิ่มต่อจากไฟล์ที่มีอยู่
Needs the Node.js runtime — open in StackBlitz to run.
การตรวจสอบการมีอยู่และ stats
หัวข้อที่มีชื่อว่า “การตรวจสอบการมีอยู่และ stats”Needs the Node.js runtime — open in StackBlitz to run.
fs/promises เทียบกับ sync — การ block event loop
หัวข้อที่มีชื่อว่า “fs/promises เทียบกับ sync — การ block event loop”readFileSync ไม่ได้แค่ “อ่านไฟล์ช้ากว่า” — มัน รันจนจบก่อนที่สิ่งอื่นใดใน process จะรันได้ รวมถึง timer ที่ถูกตั้งไว้แล้วด้วย delay 0 ms readFile (แบบ promise) ส่งงานอ่านออกไปแล้ว return ทันที ปล่อยให้ code synchronous ที่เหลือ — และ event loop — เดินหน้าต่อไปได้ในขณะที่การอ่านเกิดขึ้นเบื้องหลัง:
Output (verified on Node 24)
sync read length: 5000000
timer fired before readFileSync returned: false
next line ran before the read settledreadFile เทียบกับ stream สำหรับไฟล์ขนาดใหญ่ — memory
หัวข้อที่มีชื่อว่า “readFile เทียบกับ stream สำหรับไฟล์ขนาดใหญ่ — memory”readFile() (sync หรือ promise) จอง buffer หนึ่งก้อนที่เก็บทั้งไฟล์ สำหรับไฟล์ขนาด 50 MB นั่นหมายถึงการจอง 50 MB หนึ่งครั้ง เก็บไว้ใน memory จนกว่าคุณจะใช้เสร็จ stream กลับส่งมอบไฟล์เป็น chunk ที่มีขอบเขต — ดีฟอลต์ไม่เกิน highWaterMark (64 KiB) ต่อครั้ง — ดังนั้นการใช้ memory จึงคงที่โดยประมาณไม่ว่าไฟล์จะขนาดเท่าไหร่:
Output (verified on Node 24)
readFile() loaded bytes in one buffer: 52428800
stream delivered bytes across 800 chunks
largest single chunk (bytes): 65536
total bytes match: trueสำหรับไฟล์ 50 MB readFile ต้องการการจอง 50 MB ที่ต่อเนื่องกันหนึ่งก้อน; stream ไม่เคยถือมากกว่า 64 KiB ต่อครั้ง — ความแตกต่างที่สำคัญเมื่อไฟล์อาจมีขนาดหลายกิกะไบต์ หรือเมื่อ request หลายร้อยตัวกำลังอ่านไฟล์พร้อมกัน
Glob และการเคลียร์ handle ใน Node สมัยใหม่
หัวข้อที่มีชื่อว่า “Glob และการเคลียร์ handle ใน Node สมัยใหม่”API ใหม่สองตัวช่วยปลด dependency เก่า: fs.glob (stable ใน Node 22+) แทน package glob และ file handle รองรับ await using (Node 24+) จึงปิดตัวเองได้:
import { writeFileSync, unlinkSync } from 'node:fs';import { glob, open } from 'node:fs/promises';
writeFileSync('./config.json', JSON.stringify({ port: 3000 })); // demo fixture
// Find files without the `glob` packagefor await (const file of glob('src/**/*.test.js')) { console.log(file);}
// The handle is closed when the block exits — even if it throws{ await using fh = await open('./config.json'); const text = await fh.readFile('utf8'); console.log(JSON.parse(text).port);}
unlinkSync('./config.json');Synchronous reads — เฉพาะตอน startup
หัวข้อที่มีชื่อว่า “Synchronous reads — เฉพาะตอน startup”import { readFileSync, writeFileSync } from 'node:fs';
writeFileSync('./config.json', JSON.stringify({ port: 3000 })); // demo fixture
// Acceptable at startup — process has not yet started accepting requestsconst config = JSON.parse(readFileSync('./config.json', 'utf8'));console.log(config.port);อย่าเรียก readFileSync ใน request handler, route callback หรือ function ใดที่ถูกเรียกหลังจาก server เริ่มทำงาน
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”flowchart TB Call["fs call"] --> Sync["readFileSync / writeFileSync"] Call --> Async["readFile / writeFile (fs/promises)"] Sync -->|"runs to completion, no one else can run"| Blocks["blocks the event loop — every other request waits"] Async -->|"handed to the threadpool, returns a pending Promise"| Free["event loop keeps handling other work"] Blocks -->|"acceptable"| Startup["CLI scripts / before server.listen()"] Free -->|"required"| Server["server request handlers"]
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ทางเลือก | ข้อดี | ต้นทุน |
|---|---|---|
fs sync (readFileSync) | code ง่ายที่สุด — ไม่มี callback ไม่มี await ไม่มีการไป-กลับกับ threadpool | block ทั้ง process ตลอดระยะเวลาของ call — ยอมรับไม่ได้เมื่อ server เริ่มรับ request แล้ว |
fs/promises (readFile) | ไม่เคย block event loop; ประกอบกับ async/await ได้ | มีการไป-กลับกับ threadpool เพิ่มอีกหนึ่งครั้งต่อ call — latency สูงกว่า sync เล็กน้อยสำหรับ call เดี่ยวใน script ที่ไม่มีการทำงานพร้อมกัน |
readFile() ทั้งไฟล์ | code ง่ายที่สุดเท่าที่จะทำได้ — buffer เดียว ไม่ต้อง wire event | การใช้ memory scale ตามขนาดไฟล์; ไฟล์ 1 GB ต้องการ buffer 1 GB |
createReadStream() | memory คงที่ไม่ว่าไฟล์ขนาดเท่าไหร่; เริ่มประมวลผลได้ก่อนที่ไฟล์จะอ่านเสร็จ | code มากกว่า — คุณต้องจัดการ 'data'/'end'/'error' เอง (หรือใช้ pipeline ที่ครอบคลุมในโมดูล streams) |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “readFileSync ก็แค่ readFile ที่ช้ากว่า” — ความจริง: ความแตกต่างไม่ใช่ความเร็ว แต่คือ
readFileSyncblock ทั้ง process — ไม่มี timer, ไม่มี request อื่น, ไม่มี I/O callback ไหนทำงานได้จนกว่ามันจะ return - “readFileSync แบบเร็ว ๆ ไม่เป็นไรเพราะใช้แค่ไม่กี่มิลลิวินาที” — ความจริง: มิลลิวินาทีเหล่านั้นถูกขโมยไปจากทุก request ที่ server กำลังจัดการอยู่ ณ ขณะนั้น ไม่ใช่แค่ตัวที่ทำให้เกิดการอ่าน
- “readFile() กับ stream ใช้ memory พอ ๆ กันเพราะอ่านไฟล์เดียวกัน” — ความจริง:
readFile()ถือทั้งไฟล์เป็น buffer เดียว; stream ถือ chunkhighWaterMark(ดีฟอลต์ 64 KiB) อย่างมากหนึ่งอันต่อครั้ง ไม่ขึ้นกับขนาดไฟล์ทั้งหมด
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”- route handler หรือ function ใดก็ตามที่ถูกเรียกหลัง
server.listen()ใช้readFileSync,writeFileSyncหรือexistsSyncหรือเปล่า? ตัวไหนก็ตามในนี้ block ทุก request ที่กำลังทำงานพร้อมกันตลอดระยะเวลาของ call - code โหลดไฟล์ที่อาจมีขนาดใหญ่หรือมาจากผู้ใช้ด้วย
readFile()เข้า buffer เดียว แทนที่จะ stream มัน ทั้งที่ไฟล์จะถูกส่งต่อหรือประมวลผลทีละ chunk อยู่แล้วหรือเปล่า? - file handle ที่เปิดด้วย
fs.open()/fsPromises.open()ถูกปิดในทุกเส้นทางที่ออก รวมถึง error ที่ถูก throw หรือเปล่า — หรือawait using(Node 24+) จะทำให้มันเกิดขึ้นอัตโนมัติได้?
🐛 หา bug ในโค้ดนี้
import { readFileSync } from 'node:fs';import http from 'node:http';
var server = http.createServer(function(req, res) {// reads the config file from disk on every single requestvar config = JSON.parse(readFileSync('./config.json', 'utf8'));res.end(JSON.stringify({ port: config.port }));});
server.listen(3000);เฉลย
readFileSync รันภายใน request handler ดังนั้นทุก request ที่เข้ามา block ทั้ง event loop ตลอดระยะเวลาของการอ่านดิสก์ — ไม่มี request อื่นถูกรับหรือตอบกลับได้ในขณะที่มันทำงานอยู่ แม้แต่ตัวที่ไม่เกี่ยวข้องกับ route นี้เลย วิธีแก้คืออ่าน config ครั้งเดียวตอน startup (ก่อน server.listen() ที่ sync call ยอมรับได้) แล้วใช้ object ที่ parse แล้วซ้ำจากตัวแปรในทุก request หรือใช้ readFile ของ fs/promises พร้อม in-memory cache เล็ก ๆ ถ้าไฟล์เปลี่ยนได้ตอน runtime