process & env
process object
หัวข้อที่มีชื่อว่า “process object”process เป็น global object ที่ให้โปรแกรมของคุณเข้าถึง Node.js process ที่กำลังทำงานอยู่ มันใช้งานได้ทุกที่ — ไม่ต้อง import
property/method ที่ใช้บ่อยที่สุด:
| Property / Method | สิ่งที่ได้รับ |
|---|---|
process.argv | array ของ command-line arguments |
process.env | object ของ environment variables |
process.cwd() | string ของ current working directory |
process.exit(code) | สิ้นสุด process ด้วย exit code |
process.platform | string platform ของ OS ('linux', 'darwin', 'win32') |
process.version | string version ของ Node.js (เช่น 'v20.11.0') |
process.argv — command-line arguments
หัวข้อที่มีชื่อว่า “process.argv — command-line arguments”process.argv เป็น array เสมอ สององค์ประกอบแรกคงที่:
argv[0]— path ไปยัง binarynodeargv[1]— path ไปยัง script ที่กำลังรันargv[2]เป็นต้นไป — argument จริงของคุณ
Needs the Node.js runtime — open in StackBlitz to run.
process.env — environment variables
หัวข้อที่มีชื่อว่า “process.env — environment variables”process.env เป็น plain object ที่ key และ value เป็น string ทั้งหมด มันถูกเติมข้อมูลจาก shell environment ตอน process เริ่มทำงาน
Needs the Node.js runtime — open in StackBlitz to run.
ทุกค่าเป็น string — กับดัก PORT > 1024
หัวข้อที่มีชื่อว่า “ทุกค่าเป็น string — กับดัก PORT > 1024”ทุก key และ value ใน process.env เป็น string แม้ shell จะตั้งค่าให้ดูเหมือนตัวเลขหรือ boolean ก็ตาม PORT=3000 ใน environment จะให้ process.env.PORT === '3000' ไม่ใช่ 3000 relational operator (<, >) จะ coerce string operand เป็นตัวเลขให้อัตโนมัติ แต่ === และ + ไม่ทำ — ความไม่ตรงกันนี้เป็นหนึ่งในบั๊ก process.env ที่พบบ่อยที่สุด:
Output (verified on Node 24)
typeof PORT: string
PORT === 3000: false
PORT === "3000": true
PORT + 1: 30001
Number(PORT) + 1: 3001
PORT > 1024: true
unset PORT > 1024: false
Number(unset PORT): NaNโหลดไฟล์ .env โดยไม่ใช้ dotenv
หัวข้อที่มีชื่อว่า “โหลดไฟล์ .env โดยไม่ใช้ dotenv”ตั้งแต่ Node 22 runtime โหลดไฟล์ env ได้เอง — จาก CLI ด้วย --env-file หรือจาก code ด้วย process.loadEnvFile():
node --env-file=.env src/app.jsimport { writeFileSync, unlinkSync } from 'node:fs';import { loadEnvFile } from 'node:process';
writeFileSync('.env', 'PORT=4000\n'); // demo fixtureloadEnvFile(); // defaults to ./.envconsole.log(process.env.PORT);unlinkSync('.env');process.cwd() และ process.platform
หัวข้อที่มีชื่อว่า “process.cwd() และ process.platform”Needs the Node.js runtime — open in StackBlitz to run.
process.exit()
หัวข้อที่มีชื่อว่า “process.exit()”process.exit(code) สิ้นสุด process ทันที ตามธรรมเนียม:
- exit code
0แปลว่าสำเร็จ - exit code ที่ไม่ใช่ 0 (โดยทั่วไปคือ
1) แปลว่าล้มเหลว
// @expect-throw// Example: exit with error if a required env var is missingif (!process.env.DATABASE_URL) { console.error('ERROR: DATABASE_URL is required'); process.exit(1);}การเรียก process.exit() คือการหยุดแบบทันที — มันข้าม pending async callback และ finally block ใด ๆ ที่ยังอยู่บน call stack แนะนำให้ตั้งค่า process.exitCode แทน (เช่น process.exitCode = 1) แล้วปล่อยให้ process จบตามธรรมชาติ — นั่นให้โอกาส pending I/O, finally block และ graceful shutdown (เช่น server.close()) ได้ทำงาน
Output (verified on Node 24)
exitCode set to 1 (tentative)
setImmediate callback ran -- exitCode does NOT skip pending callbacksOutput (verified on Node 24)
about to hard-exit with process.exit(1)setImmediate ตัวเดียวกัน แต่ผลลัพธ์ต่างกันสองแบบ: process.exitCode ปล่อยให้มันทำงานก่อน process จะจบ; process.exit() ตัดมันทิ้งกลางคัน
Signals และ graceful shutdown
หัวข้อที่มีชื่อว่า “Signals และ graceful shutdown”Orchestrator (Docker, Kubernetes, systemd, Ctrl+C ใน terminal) หยุด process ด้วยการส่ง signal ไม่ใช่การฆ่าทิ้งตรง ๆ: SIGTERM คือการขอแบบสุภาพ SIGINT คือสิ่งที่ Ctrl+C ส่งมา Node ให้คุณดักจับทั้งสองด้วย process.on() เพื่อทำงานที่ค้างอยู่ให้เสร็จ — หยุดรับการเชื่อมต่อใหม่ ปล่อยให้ request ที่มีอยู่เสร็จ ปิดการเชื่อมต่อฐานข้อมูล — ก่อนที่จะจบจริง ๆ:
Output (verified on Node 24)
server started (simulated)
this line runs synchronously -- the signal handler fires after it
SIGTERM received -- closing server gracefully
cleanup done, exiting 0process.nextTick — มันอยู่ตรงไหนใน event loop
หัวข้อที่มีชื่อว่า “process.nextTick — มันอยู่ตรงไหนใน event loop”process.nextTick(fn) เข้าคิว fn ให้ทำงาน ก่อน ที่ event loop จะไปยัง phase ถัดไป — ก่อนแม้แต่ microtask อื่น (promise callback) ที่เข้าคิวไว้ก่อนหน้า มันถูกครอบคลุมอย่างละเอียด พร้อมลำดับเต็มเทียบกับ Promise, setImmediate และ setTimeout ใน Microtasks & Macrotasks สรุปสั้น ๆ ที่เกี่ยวข้องตรงนี้: process คือที่ที่ nextTick อาศัยอยู่ แต่จริง ๆ แล้วมันเป็น primitive ของ event loop ไม่ใช่ feature เฉพาะของ process — มันอยู่บน process ด้วยเหตุผลทางประวัติศาสตร์ (มันมีมาก่อน queueMicrotask)
prefix node: — ทบทวน
หัวข้อที่มีชื่อว่า “prefix node: — ทบทวน”ตามที่ครอบคลุมไปแล้วในภาพรวมของโมดูลนี้ process ไม่ต้อง import เลย — มันเป็น global จริง เหมือน Buffer module ทุกตัวที่ใช้ในหน้านี้ (node:util, node:fs, node:child_process) สามารถ import ได้ทั้งมีและไม่มี prefix node: ถ้า มันเป็น module รุ่นเก่า; built-in ใหม่เอี่ยมอย่าง node:test และ node:sqlite resolve ได้เฉพาะที่มี prefix เท่านั้น เมื่อคุณเห็น require('util') ใน code เก่า มันเทียบเท่ากับ require('node:util') — prefix เป็น convention เพื่อความชัดเจน ไม่ใช่การเปลี่ยนพฤติกรรม สำหรับอะไรก็ตามที่มีมาก่อนมัน
การ parse CLI และการจัดสไตล์ terminal: util.parseArgs และ util.styleText
หัวข้อที่มีชื่อว่า “การ parse CLI และการจัดสไตล์ terminal: util.parseArgs และ util.styleText”node:util มาพร้อม utility เล็ก ๆ สองตัวที่ลดความจำเป็นในการใช้ npm package ยอดนิยมสำหรับ CLI tool ง่าย ๆ parseArgs (stable ตั้งแต่ 20.x) แทน yargs/minimist สำหรับการ parse flag ตรงไปตรงมา:
Output (verified on Node 24)
[Object: null prototype] { name: 'Ada', verbose: true, count: '3' }styleText (stable ตั้งแต่ 22.x) เพิ่ม ANSI color/style code ให้ string — แทน chalk/picocolors สำหรับกรณีพื้นฐาน มันตรวจสอบอัตโนมัติว่า stream ปลายทางรองรับสีจริงหรือไม่ และ คืนค่า string เปล่าไม่เปลี่ยนแปลงถ้าไม่รองรับ (piped output, log file, CI runner ส่วนใหญ่):
Output (verified on Node 24)
"OK"
length with codes: 2 vs plain length: 2Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”flowchart TB Env["process.env.PORT"] -->|"always"| Str["a string, never a number"] Str -->|"=== or arithmetic"| Trap["silent bug: false / string concat"] Str -->|"Number() first"| Safe["correct comparison/arithmetic"] Exit1["process.exitCode = n"] -->|"lets pending callbacks + finally run"| Natural["process exits naturally"] Exit2["process.exit(n)"] -->|"cuts off immediately"| Hard["pending callbacks + finally skipped"] Sig["SIGTERM / SIGINT"] -->|"no listener"| Immediate["process dies immediately, mid-request"] Sig -->|"process.on(signal, ...)"| Graceful["finish in-flight work, then exit(0)"]
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”| ทางเลือก | ข้อดี | ต้นทุน |
|---|---|---|
อ่าน config ผ่าน process.env | code เดียวกัน deploy ได้ทุก environment (12-Factor); ไม่ต้อง rebuild สำหรับ config ใหม่ | ทุกอย่างเป็น string — ค่าตัวเลข/boolean ทุกตัวต้องแปลงและ validate อย่างชัดเจน |
process.exitCode = n | pending I/O, timer และ finally block ได้ทำงาน; ดีฟอลต์ที่ปลอดภัยกว่า | ต้องการให้ event loop drain จริง ๆ — handle ที่ค้างอยู่จะทำให้ process ไม่จบ |
process.exit(n) | รับประกันการสิ้นสุดทันที มีประโยชน์เมื่อมีอะไรค้างจริง ๆ | ข้าม finally block และ pending callback แบบไม่มีเงื่อนไข — รวมถึงตัวที่ flush log หรือปิดการเชื่อมต่อ |
จัดการ SIGTERM เอง | request ที่ค้างอยู่ได้เสร็จ; การเชื่อมต่อปิดอย่างสะอาด | เพิ่ม code เส้นทาง shutdown ที่พลาดง่าย (ต้องเรียก exit() จริง ๆ ในที่สุด ไม่งั้น grace period ของ orchestrator หมดแล้วมันจะ SIGKILL คุณอยู่ดี) |
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “process.env.PORT > 1024 ไม่ปลอดภัยเพราะ PORT เป็น string” — ความจริง: relational operator (
</>) coerce string operand เป็นตัวเลขให้อัตโนมัติ; กับดักจริง ๆ คือ===(ไม่ coerce เลย) และ+(concatenate string แทนที่จะบวก) - “process.exit() กับการตั้ง process.exitCode ทำสิ่งเดียวกัน แค่ syntax ต่างกัน” — ความจริง:
exitCodeปล่อยให้ event loop drain ตามธรรมชาติ — pending callback และfinallyblock ได้ทำงาน;exit()สิ้นสุดทันที ข้ามทั้งสองอย่าง - “ถ้าไม่มี signal handler Kubernetes จะรอให้ process ของฉันทำ request ปัจจุบันเสร็จ” — ความจริง: ถ้าไม่มี listener
SIGTERMพฤติกรรมดีฟอลต์จะสิ้นสุด process ทันที; มีแค่ handler ที่ชัดเจนเท่านั้นที่ซื้อเวลาทำงานที่ค้างอยู่ให้เสร็จ และซื้อได้แค่ในขอบเขต grace period ของ orchestrator - “styleText เพิ่ม color code ให้ string ของฉันเสมอ” — ความจริง: มันตรวจสอบก่อนว่า stream ปลายทางรองรับสีหรือไม่ และคืนค่า string เปล่าแบบเงียบ ๆ ถ้าไม่รองรับ (piped output, log file, CI ส่วนใหญ่)
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”- code เปรียบเทียบ
process.env.SOMETHINGด้วย===กับตัวเลข หรือส่งมันเข้าคำนวณโดยตรงด้วย+โดยไม่มีการแปลงNumber(...)/parseInt(...)อย่างชัดเจนก่อนหรือเปล่า? - script เรียก
process.exit()ทันทีหลังconsole.log()หรือการเขียนfs.writeFile()/database ที่อาจยังค้างอยู่หรือเปล่า? บาง platform buffered stdout อาจถูกตัดทอน และ async write ที่ค้างอยู่จะถูกทิ้งไปเลย - server ที่รันระยะยาวลงทะเบียน
SIGTERM/SIGINThandler ไว้เลยหรือเปล่า? การไม่มีมันแปลว่าทุกครั้งที่ deploy จะฆ่า request ที่ค้างอยู่แทนที่จะทำให้เสร็จ - logic การเคลียร์อยู่ใน
finallyblock ที่รันเฉพาะเส้นทางออกตามธรรมชาติ ในขณะที่ branch ก่อนหน้าเรียกprocess.exit()ตรง ๆ — ข้ามการเคลียร์เดียวกันนั้นไปแบบเงียบ ๆ หรือเปล่า?
🐛 หา bug ในโค้ดนี้
import http from 'node:http';
var server = http.createServer(function(req, res) {res.end('ok');});
server.listen(process.env.PORT || 3000, function() {console.log('listening on port', process.env.PORT || 3000);});เฉลย
process.env.PORT เป็น string (หรือ undefined) server.listen() บังเอิญรับ string port number ได้ ดังนั้น call นี้เฉพาะเจาะจงไม่ crash — แต่ทันทีที่ค่านี้ถูกใช้ที่ไหนก็ตามด้วย ===, การคำนวณ หรือการตรวจ type (typeof port === 'number', validation schema, health-check ที่เทียบกับช่วงตัวเลข) type ที่เป็น string จะทำให้เกิดบั๊กแบบเงียบ วิธีแก้คือแปลงครั้งเดียวใกล้ด้านบน: var port = Number(process.env.PORT) || 3000; เพื่อให้การใช้ port ทุกครั้งในภายหลังเป็นตัวเลขจริง