JavaScript
Error Handling
RangeError — เมื่อค่าอยู่นอกช่วงที่ยอมรับได้
RangeError เกิดเมื่อค่าที่ส่งให้ method อยู่นอกช่วง — toFixed(-1), array ความยาวติดลบ — วิธีตรวจสอบก่อนใช้งาน
RangeError คืออะไร
RangeError เป็น Error ประเภทหนึ่งที่เกิดเมื่อ**ค่าที่ส่งให้ method หรือ operation อยู่นอกช่วงที่ยอมรับได้** จุดที่ทำให้ RangeError ต่างจาก Error ประเภทอื่นคือ **ปัญหาไม่ได้อยู่ที่ syntax, type, หรือการประกาศตัวแปร — แต่อยู่ที่ค่าที่ "เกินขอบเขต" ของสิ่งที่ method หรือ operation นั้นรองรับ** คิดง่าย ๆ: method แต่ละตัวถูกออกแบบมาให้รับค่าในช่วงหนึ่ง — เหมือนประตูที่กว้าง 80 ซม. ถ้าเราพยายามเอาของกว้าง 2 เมตรเข้าประตู มันก็เข้าไม่ได้ ไม่ใช่เพราะของนั้นผิด type หรือเราเปิดประตูผิดวิธี — แต่เพราะของมันกว้างเกินไป (เกินช่วงที่ประตูรับได้) เมื่อเจอ RangeError JavaScript จะ: 1. สร้าง Error object ที่มี `.name` = `"RangeError"` 2. แสดง `.message` ที่อธิบายว่าค่าไหนเกินช่วง และช่วงที่ถูกต้องคืออะไร 3. **หยุดการทำงานที่บรรทัดนั้น** — โค้ดบรรทัดถัดไปใน scope เดียวกันจะไม่ทำงาน RangeError เกิด**ตอน runtime** (เหมือน TypeError และ ReferenceError ต่างจาก SyntaxError) — JavaScript อ่าน syntax ผ่านแล้ว แต่ตอนรันถึงบรรทัดนั้น ค่าที่ส่งให้ method เกินขอบเขตที่รับได้ **ต่างจาก Error อื่นอย่างไร**: - **SyntaxError**: syntax ผิด — โค้ดอ่านไม่รู้เรื่องตั้งแต่แรก - **TypeError**: ใช้ค่าผิด type — ตัวแปรมีอยู่แต่ type ไม่รองรับ (เช่นเรียก `"hello"()` — string ไม่ใช่ function) - **ReferenceError**: ตัวแปรไม่มีอยู่เลยใน scope — JavaScript หาชื่อไม่เจอ - **RangeError**: ตัวแปรมีอยู่ type ถูกต้อง syntax ถูกต้อง — แต่**ค่าของมันอยู่นอกช่วงที่ method หรือ operation นั้นยอมรับได้**
RangeError เกิดตอน runtime — JavaScript รู้ syntax แล้ว type ก็ถูกต้อง แต่ค่าที่ส่งให้ method เกินขอบเขตที่รับได้
// ❌ สร้าง array ที่มีความยาวเป็นลบ — RangeError
new Array(-1);
// RangeError: Invalid array length
// .name = "RangeError"
// .message = "Invalid array length"
// ^ -1 เป็นตัวเลข (type ถูกต้อง) — แต่ความยาว array ต้อง >= 0
// การใช้ -1 จึง "เกินช่วง" ที่ Array constructor รับได้
// ❌ กำหนด array length เป็นค่าลบ — RangeError
let arr = [1, 2, 3];
arr.length = -5;
// RangeError: Invalid array length
// ^ arr มีอยู่จริง type ก็ถูกต้อง — แต่ length ติดลบไม่ได้
console.log("บรรทัดนี้จะไม่ทำงาน");
// ^ RangeError หยุดการทำงานที่บรรทัดที่เกิด error
// โค้ดบรรทัดถัดไปใน scope เดียวกันจะไม่ทำงาน- RangeError เกิดเมื่อ**ค่าอยู่นอกช่วงที่ยอมรับได้** — method หรือ operation รองรับค่าแค่บางช่วง และค่าที่ส่งมาหลุดออกนอกช่วงนั้น
- RangeError เกิดตอน **runtime** — โค้ด syntax ถูกต้อง type ก็ถูกต้อง แต่ค่าที่ส่งเกินขอบเขต (เหมือน TypeError, ReferenceError — ต่างจาก SyntaxError)
- `.name` = `"RangeError"` — บอกว่าเป็น error เกี่ยวกับค่าที่อยู่นอกช่วง (range)
- RangeError **หยุดการทำงานที่บรรทัดที่เกิด error** — โค้ดก่อนหน้าทำงานแล้ว แต่โค้ดถัดไปใน scope เดียวกันจะไม่ทำงาน
- error message ของ RangeError มักบอกช่วงที่ถูกต้อง — เช่น `"Invalid array length"`, `"digits argument must be between 0 and 100"` — อ่านแล้วรู้ทันทีว่าต้องแก้ค่าอย่างไร
- ต่างจาก TypeError: TypeError = ใช้ค่าผิด type, RangeError = ใช้ค่าถูก type แต่อยู่นอกช่วง — `new Array(-1)` type ถูก (number) แต่ค่าเกินช่วง
กรณีที่พบบ่อยของ RangeError
RangeError เกิดได้หลายรูปแบบ — แต่ทั้งหมดมีสาเหตุร่วมกันคือ **ค่าที่ส่งให้ method หรือ operation เกินขอบเขตที่มันรับได้** มาดู 4 กรณีที่พบบ่อยที่สุด (ยกเว้น Maximum call stack size exceeded — จะลงลึกในหัวข้อถัดไป) — แต่ละกรณีจะมีทั้งตัวอย่างที่ผิด (❌) และวิธีแก้ (✅) ให้เห็นภาพชัดเจน ทุกตัวอย่างด้านล่างนี้โค้ดก่อนหน้าทำงานได้ปกติ — จะเกิด RangeError เฉพาะตรงบรรทัดที่ส่งค่าเกินช่วง
Array constructor และ .length รับได้เฉพาะจำนวนเต็มที่ไม่ติดลบและไม่เกิน 2^32-1 (ประมาณ 4.29 พันล้าน) — ค่าลบ, Infinity, หรือค่าที่มากเกินจะเกิด RangeError
// ❌ new Array() รับความยาวติดลบไม่ได้ — RangeError
new Array(-1);
// RangeError: Invalid array length
// ❌ new Array() รับ Infinity ไม่ได้ — RangeError
new Array(Infinity);
// RangeError: Invalid array length
// ❌ กำหนด .length เป็นค่าลบ — RangeError
let items = [1, 2, 3];
items.length = -5;
// RangeError: Invalid array length
// ❌ กำหนด .length เป็น Infinity — RangeError
items.length = Infinity;
// RangeError: Invalid array length
// ✅ ใช้จำนวนเต็ม >= 0 (และไม่เกิน 2^32-1)
let arr1 = new Array(5);
console.log(arr1); // [ <5 empty items> ]
console.log(arr1.length); // 5
// ✅ กำหนด .length เป็น 0 — ล้าง array ได้ (0 เป็นค่าที่ถูกต้อง)
let arr2 = [1, 2, 3, 4, 5];
arr2.length = 0;
console.log(arr2); // [] — array ว่างเปล่า
// ✅ ใช้ [] สร้าง array แบบ literal — ไม่ต้องระบุ length
let arr3 = [10, 20, 30];
console.log(arr3.length); // 3toFixed(digits) รับ digits ได้แค่ 0 ถึง 100 — น้อยกว่า 0 หรือมากกว่า 100 จะเกิด RangeError
// ❌ toFixed() รับค่าเกิน 100 — RangeError
(1.23).toFixed(101);
// RangeError: toFixed() digits argument must be between 0 and 100
// ❌ toFixed() รับค่าติดลบ — RangeError
(1.23).toFixed(-1);
// RangeError: toFixed() digits argument must be between 0 and 100
// ❌ toFixed() รับ Infinity — RangeError
(1.23).toFixed(Infinity);
// RangeError: toFixed() digits argument must be between 0 and 100
// ✅ toFixed() รับค่า 0-100 ได้ทั้งหมด
console.log((1.23).toFixed(0)); // "1"
console.log((1.23).toFixed(2)); // "1.23"
console.log((1.23).toFixed(5)); // "1.23000"
console.log((99.9).toFixed(0)); // "100"
console.log((0.456).toFixed(1)); // "0.5"
console.log((3.14159).toFixed(3)); // "3.142"
// 💡 toFixed() ที่ใช้บ่อยมักเป็น 0, 1, 2 — ไม่มีเหตุผลต้องใช้ 100
// RangeError จาก toFixed มักเกิดจากการคำนวณผิดหรือบัคในโค้ด
// มากกว่าการตั้งใจใช้ค่ามาก ๆtoPrecision(precision) รับ precision ได้แค่ 1 ถึง 100 — 0, ติดลบ, หรือมากกว่า 100 จะเกิด RangeError
// ❌ toPrecision() รับ 0 ไม่ได้ — ต้องเริ่มที่ 1
(1.23).toPrecision(0);
// RangeError: toPrecision() argument must be between 1 and 100
// ❌ toPrecision() รับค่าติดลบ — RangeError
(1.23).toPrecision(-3);
// RangeError: toPrecision() argument must be between 1 and 100
// ❌ toPrecision() รับเกิน 100 — RangeError
(1.23).toPrecision(101);
// RangeError: toPrecision() argument must be between 1 and 100
// ✅ toPrecision() รับค่า 1-100
console.log((1.23).toPrecision(1)); // "1"
console.log((1.23).toPrecision(2)); // "1.2"
console.log((1.23).toPrecision(3)); // "1.23"
console.log((1.23).toPrecision(5)); // "1.2300"
// ✅ ใช้กับเลขที่มีหลายหลัก — เห็นผลเรื่องจำนวนหลักนัยสำคัญชัด
console.log((123.456).toPrecision(4)); // "123.5"
console.log((0.00123).toPrecision(2)); // "0.0012"
// 💡 toPrecision กับ toFixed ต่างกัน:
// toFixed(n) = แสดงทศนิยม n ตำแหน่ง (นับจากจุดทศนิยม)
// toPrecision(n) = แสดงทั้งหมด n หลัก (นับทุกหลักรวมหน้าจุดทศนิยม)repeat(count) รับ count ได้เฉพาะจำนวนเต็ม >= 0 ที่ไม่ใช่ Infinity — ค่าลบหรือ Infinity จะเกิด RangeError
// ❌ repeat() รับค่าติดลบ — RangeError
"hello".repeat(-1);
// RangeError: Invalid count value: -1
// ❌ repeat() รับ Infinity — RangeError
"hello".repeat(Infinity);
// RangeError: Invalid count value: Infinity
// ✅ repeat() รับ 0 ขึ้นไป (จำนวนเต็ม)
console.log("hello".repeat(0)); // "" (string ว่าง)
console.log("hello".repeat(1)); // "hello"
console.log("hello".repeat(3)); // "hellohellohello"
console.log("Ab".repeat(2)); // "AbAb"
// ✅ ใช้ repeat() ทำเส้นคั่น — pattern ที่พบบ่อยในชีวิตจริง
console.log("-".repeat(10)); // "----------"
console.log("*".repeat(5)); // "*****"
console.log("= ".repeat(4)); // "= = = = "
// 💡 repeat(count) — count ต้องเป็นจำนวนเต็มที่ >= 0
// ถ้า count เป็นทศนิยม — JavaScript จะปัดลงเป็นจำนวนเต็มให้ (ไม่เกิด error)
console.log("hi".repeat(2.7)); // "hihi" — 2.7 ถูกปัดลงเป็น 2
console.log("hi".repeat(0.9)); // "" — 0.9 ถูกปัดลงเป็น 0นอกจาก 4 กรณีด้านบน ยังมีอีกกรณีที่พบบ่อยมากคือ **Maximum call stack size exceeded** — เกิดจากการเขียน recursion ที่ไม่มีจุดหยุด (base case) ทำให้ฟังก์ชันเรียกตัวเองไปเรื่อย ๆ จน call stack เต็ม กรณีนี้สำคัญพอที่จะลงรายละเอียดแยกในหัวข้อถัดไป — เพราะการเข้าใจมันต้องเข้าใจ call stack และ recursion ก่อน
- **Invalid array length**: `new Array()` และ `.length` ต้องเป็นจำนวนเต็ม >= 0 และไม่เกิน 2^32-1 — ติดลบ, Infinity, หรือค่าที่มากเกินจะเกิด RangeError
- **toFixed(digits)**: รับ digits ได้ 0-100 — น้อยกว่า 0 หรือมากกว่า 100 = RangeError — error message บอกชัด: `"digits argument must be between 0 and 100"`
- **toPrecision(precision)**: รับ precision ได้ 1-100 — 0 หรือมากกว่า 100 = RangeError — ต่างจาก toFixed ตรงที่เริ่มที่ 1 ไม่ใช่ 0
- **String.repeat(count)**: รับ count >= 0 ที่ไม่ใช่ Infinity — ติดลบหรือ Infinity = RangeError: `"Invalid count value"` — count เป็นทศนิยมได้ (จะถูกปัดลง)
- **Maximum call stack size exceeded**: recursion ที่ไม่มี base case ทำให้ call stack ล้น — จะอธิบายละเอียดในหัวข้อถัดไป
Maximum call stack size exceeded — recursion ที่ไม่หยุด
**Maximum call stack size exceeded** เป็น RangeError ที่เกิดจากสาเหตุที่ต่างจากกรณีก่อนหน้า — ไม่ได้เกิดจากการส่งค่าเกินช่วงให้ method แต่เกิดจากการ**เรียกฟังก์ชันซ้อนกันมากเกินไปจน call stack เต็ม** **Call stack คืออะไร** — เวลา JavaScript เรียกฟังก์ชัน มันจะวาง "stack frame" ของฟังก์ชันนั้นลงบน call stack (คล้ายกองจาน — วางซ้อนกัน) เมื่อฟังก์ชันทำงานเสร็จ (return) frame นั้นจะถูกเอาออกจาก stack ถ้ามีฟังก์ชันเรียกซ้อนกัน 3 ชั้น stack ก็จะมี 3 frames **Recursion คืออะไร** — recursion คือฟังก์ชันที่**เรียกตัวเอง** — เป็นเทคนิคที่มีประโยชน์มากในการแก้ปัญหาบางประเภท (เช่น tree, การค้นหา) แต่ recursion ที่ถูกต้องต้องมี **base case** (เงื่อนไขหยุด) — ถ้าไม่มี base case ฟังก์ชันจะเรียกตัวเองไปเรื่อย ๆ ไม่มีวันหยุด — แต่ละครั้งที่เรียกตัวเองจะเพิ่ม frame ใน call stack — จนในที่สุด call stack ก็เต็ม → RangeError: Maximum call stack size exceeded **call stack รับได้กี่ชั้น** — ขึ้นอยู่กับ JavaScript engine และ environment — โดยทั่วไปอยู่ที่ประมาณ 10,000-50,000 frames (ใน Node.js มักน้อยกว่าใน browser) — เกินนี้จะเกิด RangeError
loop() เรียกตัวเองตลอดเวลาโดยไม่มีเงื่อนไขหยุด — call stack เพิ่มขึ้นเรื่อย ๆ จนล้น → RangeError
// ❌ recursion ที่ไม่มี base case — เรียกตัวเองไปเรื่อย ๆ
function loop() {
loop(); // เรียกตัวเอง — ไม่มีวันหยุด
}
loop();
// RangeError: Maximum call stack size exceeded
// ^ แต่ละครั้งที่ loop() เรียกตัวเอง — call stack เพิ่ม 1 frame
// เรียกซ้ำไปเรื่อย ๆ โดยไม่มีทางหยุด → stack เต็ม → RangeError
// ❌ countDown ที่ลืม base case — ดูเหมือนทำงานได้ แต่ไม่มีวันหยุด
function countDown(n) {
console.log(n);
countDown(n - 1); // n ลดลงเรื่อย ๆ — 10, 9, 8, ... , -1000, -1001, ...
// ไม่มีเงื่อนไขหยุด — วิ่งเลย 0 ลงไปติดลบไม่สิ้นสุด
}
// countDown(10);
// ^ ช่วงแรกจะแสดง 10, 9, 8, ... ไปเรื่อย ๆ
// แต่พอถึงจุดหนึ่ง call stack จะเต็ม → RangeErrorbase case คือเงื่อนไขที่บอกว่า "พอแค่นี้ ไม่ต้องเรียกตัวเองต่อ" — recursion ที่ดีต้องมี base case และ recursive call ต้อง "เข้าใกล้" base case ทุกครั้ง
// ✅ recursion ที่มี base case — หยุดเมื่อ n <= 0
function countDown(n) {
if (n <= 0) { // 👈 base case — เงื่อนไขหยุด
console.log("Done!");
return; // 👈 หยุด — ไม่เรียกตัวเองต่อ
}
console.log(n);
countDown(n - 1); // 👈 recursive call — n ลดลงทีละ 1
} // วิ่งเข้าหา base case ทุกครั้ง
countDown(5);
// output:
// 5
// 4
// 3
// 2
// 1
// Done!
// ✅ ตัวอย่าง: คำนวณผลรวม 1 ถึง n ด้วย recursion
function sumTo(n) {
if (n <= 0) return 0; // base case — n = 0 ผลรวมเป็น 0
return n + sumTo(n - 1); // recursive call — n ลดลงทีละ 1
}
console.log(sumTo(1)); // 1
console.log(sumTo(3)); // 6 (1 + 2 + 3)
console.log(sumTo(5)); // 15 (1 + 2 + 3 + 4 + 5)
// ✅ ตัวอย่าง: factorial ด้วย recursion
function factorial(n) {
if (n <= 1) return 1; // base case — 1! = 1, 0! = 1
return n * factorial(n - 1); // recursive call — n ลดลงทีละ 1
}
console.log(factorial(3)); // 6 (3 × 2 × 1)
console.log(factorial(5)); // 120 (5 × 4 × 3 × 2 × 1)ในหลายกรณี loop (for, while) ทำงานได้เหมือน recursion แต่ปลอดภัยกว่าเพราะไม่เพิ่ม call stack — ใช้ loop สำหรับ iteration ที่ไม่ซับซ้อน
// ✅ ใช้ for loop แทน recursion — ได้ผลลัพธ์เหมือนกัน ไม่เพิ่ม call stack
function countDownLoop(n) {
for (let i = n; i > 0; i--) {
console.log(i);
}
console.log("Done!");
}
countDownLoop(5);
// output: 5, 4, 3, 2, 1, Done!
// ✅ sumTo แบบ loop — ไม่เสี่ยง stack overflow แม้ n เป็นล้าน
function sumToLoop(n) {
let sum = 0;
for (let i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
console.log(sumToLoop(5)); // 15
console.log(sumToLoop(100)); // 5050
// ✅ factorial แบบ loop — ไม่มีการเรียกฟังก์ชันซ้อน
function factorialLoop(n) {
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}
console.log(factorialLoop(3)); // 6
console.log(factorialLoop(5)); // 120
// 💡 ใช้ recursion เมื่อ:
// - ปัญหามีโครงสร้างแบบ recursive โดยธรรมชาติ (tree, divide-and-conquer)
// - โค้ดอ่านง่ายกว่ามากเมื่อเขียนแบบ recursion
//
// ใช้ loop เมื่อ:
// - วนซ้ำตรงไปตรงมา (นับ 1 ถึง n, วน array)
// - จำนวนครั้งที่วนมากจน recursion อาจ overflow call stack
// - ไม่ต้องการให้โค้ดซับซ้อนเกินจำเป็น- **Call stack** คือ stack ที่เก็บ frames ของฟังก์ชันที่กำลังทำงาน — เรียกฟังก์ชัน = เพิ่ม frame, return = เอา frame ออก — stack มีขนาดจำกัด (~10,000-50,000 frames)
- **Maximum call stack size exceeded** เกิดเมื่อ call stack เต็ม — สาเหตุหลักคือ recursion ที่ไม่มี base case หรือ base case ไม่เคยถูกเข้าถึง
- **base case คือเงื่อนไขหยุดของ recursion** — ทุก recursion ต้องมี base case และ recursive call ต้อง "เข้าใกล้" base case ทุกครั้ง — เช่น `countDown(n - 1)` ทำให้ n ลดลงจนถึง 0
- **ใช้ loop แทน recursion ได้** — สำหรับการวนซ้ำธรรมดา loop ปลอดภัยกว่าเพราะไม่เพิ่ม call stack — ใช้ recursion เฉพาะเมื่อปัญหามีโครงสร้าง recursive โดยธรรมชาติ
- **Recursion มีประโยชน์** — ในปัญหาเช่น tree traversal, divide-and-conquer, หรือ backtracking — recursion ทำให้โค้ดสั้นและอ่านง่ายกว่า loop มาก — แค่ต้องมั่นใจว่ามี base case
RangeError กับ built-in methods — ช่วงค่าที่แต่ละ method รับได้
method ใน JavaScript หลายตัวมีข้อกำหนดเรื่องช่วงของ argument — เกินช่วง = RangeError ตารางต่อไปนี้สรุป built-in methods ที่สอนไปแล้วและช่วงค่าที่แต่ละตัวรับได้ — ใช้เป็น reference เวลาเจอ RangeError จะได้รู้ทันทีว่าต้องแก้ค่าอย่างไร
| Method / Operation | Argument | ช่วงที่รับได้ | ตัวอย่างค่าที่เกินช่วง → RangeError | error message |
|---|---|---|---|---|
| `new Array(length)` | `length` | จำนวนเต็ม 0 ถึง 2^32-1 | `-1`, `Infinity`, `4_300_000_000` | `Invalid array length` |
| `arr.length = value` | `value` | จำนวนเต็ม 0 ถึง 2^32-1 | `-5`, `Infinity` | `Invalid array length` |
| `Number.toFixed(digits)` | `digits` | 0 ถึง 100 | `-1`, `101`, `Infinity` | `toFixed() digits argument must be between 0 and 100` |
| `Number.toPrecision(precision)` | `precision` | 1 ถึง 100 | `0`, `-1`, `101`, `Infinity` | `toPrecision() argument must be between 1 and 100` |
| `String.repeat(count)` | `count` | จำนวนเต็ม >= 0 (ไม่ใช่ Infinity) | `-1`, `Infinity` | `Invalid count value` |
| Recursion ที่ไม่มี base case | — (จำนวน recursive calls) | ขึ้นกับ engine (~10,000-50,000) | เรียกเกิน limit ของ call stack | `Maximum call stack size exceeded` |
รูปแบบที่ใช้บ่อย: ตรวจสอบว่าค่าอยู่ในช่วงที่ method รับได้ก่อนเรียกใช้ — ถ้าไม่อยู่ในช่วงก็ใช้ค่า default หรือแจ้งเตือนแทน
// ✅ ตรวจสอบก่อนใช้ toFixed() — ใช้เฉพาะค่าที่อยู่ในช่วง 0-100
function safeToFixed(num, digits) {
if (digits < 0 || digits > 100) {
console.log("toFixed รับ digits ได้ 0-100 เท่านั้น — ตอนนี้ได้ " + digits);
return num.toString(); // fallback — คืนค่าเป็น string ธรรมดา
}
return num.toFixed(digits);
}
console.log(safeToFixed(1.23, 2)); // "1.23" — อยู่ในช่วง
console.log(safeToFixed(1.23, 101)); // "toFixed รับ digits ได้ 0-100 เท่านั้น — ตอนนี้ได้ 101"
console.log(safeToFixed(1.23, -1)); // "toFixed รับ digits ได้ 0-100 เท่านั้น — ตอนนี้ได้ -1"
// ✅ ตรวจสอบก่อนสร้าง array — ใช้เฉพาะค่าที่มากกว่าหรือเท่ากับ 0
function safeArray(length) {
if (length < 0 || !Number.isFinite(length)) {
console.log("array length ต้องเป็นจำนวนเต็ม >= 0 — ตอนนี้ได้ " + length);
return []; // fallback — คืน array ว่าง
}
return new Array(length);
}
console.log(safeArray(5)); // [ <5 empty items> ]
console.log(safeArray(-1)); // "array length ต้องเป็นจำนวนเต็ม >= 0 — ตอนนี้ได้ -1"
console.log(safeArray(Infinity)); // "array length ต้องเป็นจำนวนเต็ม >= 0 — ตอนนี้ได้ Infinity"
// ✅ ตรวจสอบก่อนใช้ repeat() — count ต้องเป็น >= 0 และไม่ใช่ Infinity
function safeRepeat(str, count) {
if (count < 0 || !Number.isFinite(count)) {
console.log("repeat count ต้องเป็นจำนวนเต็ม >= 0 — ตอนนี้ได้ " + count);
return ""; // fallback — คืน string ว่าง
}
return str.repeat(count);
}
console.log(safeRepeat("Hi", 3)); // "HiHiHi"
console.log(safeRepeat("Hi", -2)); // "repeat count ต้องเป็นจำนวนเต็ม >= 0 — ตอนนี้ได้ -2"
console.log(safeRepeat("Hi", Infinity)); // "repeat count ต้องเป็นจำนวนเต็ม >= 0 — ตอนนี้ได้ Infinity"- **toFixed(digits)** — `digits` ต้องอยู่ระหว่าง 0 ถึง 100 — เป็น method ที่มีโอกาสเกิด RangeError มากที่สุดเพราะคนมักลืมว่าเกิน 100 ไม่ได้
- **toPrecision(precision)** — `precision` ต้องอยู่ระหว่าง 1 ถึง 100 — ต่างจาก toFixed ตรงที่เริ่มที่ 1 (ไม่ใช่ 0) เพราะต้องมีอย่างน้อย 1 หลักนัยสำคัญ
- **repeat(count)** — `count` ต้อง >= 0 และไม่ใช่ Infinity — count ที่เป็นทศนิยมจะถูกปัดลงอัตโนมัติ (ไม่เกิด RangeError)
- **Array(length)** — `length` ต้องเป็นจำนวนเต็ม >= 0 และไม่เกิน 2^32-1 — `Infinity` ก็ทำให้เกิด RangeError เช่นกัน
- **Recursion** — ไม่มี argument range โดยตรง — แต่จำนวนครั้งที่เรียกตัวเองซ้อนกันต้องไม่เกิน limit ของ call stack — ป้องกันด้วย base case
- **Number.isFinite()** ใช้ตรวจสอบว่าค่าไม่ใช่ `Infinity`, `-Infinity`, หรือ `NaN` — มีประโยชน์มากก่อนส่งค่าให้ method ที่ไม่รับ Infinity
วิธีป้องกันและแก้ไข RangeError
RangeError เป็น error ที่**ป้องกันได้เกือบ 100%** — ต่างจาก TypeError หรือ ReferenceError ที่อาจเกิดจากข้อมูลจากภายนอก (API, user input) — RangeError มักเกิดจากค่าที่เราเป็นคนกำหนดเอง จึงควบคุมได้ง่าย นี่คือวิธีปฏิบัติที่ช่วยให้เจอ RangeError น้อยลงและแก้ไขได้เร็ว:
- **อ่าน error message ให้ละเอียด** — RangeError มักบอกช่วงที่ถูกต้องตรง ๆ — `"digits argument must be between 0 and 100"` แปลว่า digits ต้อง 0-100 — `"Invalid array length"` แปลว่า length ไม่ถูกต้อง — error message คือกุญแจสู่การแก้ไข
- **รู้ช่วงของ method ก่อนใช้** — toFixed รับ 0-100, toPrecision รับ 1-100, repeat รับ >= 0, Array length รับ >= 0 — ท่องจำหรือ bookmark ไว้ — หรือเปิด MDN ดูก่อนใช้ method ที่ไม่คุ้นเคย
- **ใช้ `console.log()` ตรวจสอบค่าก่อนส่งให้ method** — ถ้าไม่แน่ใจว่าค่าที่คำนวณได้อยู่ในช่วงที่รับได้หรือไม่ — ให้ `console.log()` ดูก่อน — จะเห็นทันทีว่าค่าจริงเป็นเท่าไหร่และเกินช่วงหรือไม่
- **ตรวจสอบค่าก่อนเรียก method** — ใช้ `if` ตรวจสอบว่าค่าอยู่ในช่วงก่อนเรียกใช้ — `if (digits >= 0 && digits <= 100) { num.toFixed(digits); }` — เป็นนิสัยที่ดีที่ป้องกัน RangeError ได้
- **recursion ต้องมี base case เสมอ** — ก่อนเขียน recursion ให้ถามตัวเองว่า "ฟังก์ชันนี้จะหยุดเรียกตัวเองเมื่อไหร่" — ถ้าตอบไม่ได้ แปลว่ายังไม่มี base case — อย่าเขียน recursion จนกว่าจะระบุ base case ได้ชัดเจน
- **ใช้ loop แทน recursion สำหรับการวนซ้ำธรรมดา** — for, while, do...while ไม่เพิ่ม call stack — ปลอดภัยกว่า recursion สำหรับงานนับเลข, วน array, หรือ iteration ทั่วไป
- **ใช้ `Number.isFinite()` ตรวจสอบ Infinity** — `Infinity` เป็นค่าที่ method หลายตัวไม่รับ — `Number.isFinite(value)` จะคืน `false` สำหรับ `Infinity`, `-Infinity`, และ `NaN` — ใช้ตรวจสอบก่อนส่งค่าให้ method
- **ระวังการคำนวณที่ให้ผลลัพธ์เกินช่วงโดยไม่ตั้งใจ** — เช่น `let digits = userInput * 100` — ถ้า userInput = 2 → digits = 200 → เกินช่วง toFixed — ควรตรวจสอบผลลัพธ์หลังคำนวณก่อนส่งให้ method
RangeError กับ Error ประเภทอื่น
มือใหม่มักสับสนว่า error ที่เจอเป็น RangeError, TypeError, ReferenceError หรือ SyntaxError — มาดูตารางเปรียบเทียบให้เห็นความแตกต่างชัดเจน:
| Error ประเภท | เกิดตอน | สาเหตุ | ตัวอย่าง error message | วิธีแก้ |
|---|---|---|---|---|
| SyntaxError | Parsing (ก่อนรัน) | เขียน syntax ผิดกฎของภาษา — อ่านโค้ดแล้วไม่เข้าใจ | `Unexpected token` `Unexpected end of input` | ตรวจความถูกต้องของ syntax — วงเล็บครบคู่, string ปิด, ไม่มี reserved word ผิดที่ |
| TypeError | Runtime (ตอนรัน) | ใช้ค่าที่ type ไม่รองรับการดำเนินการนั้น — ตัวแปรมีอยู่แต่ใช้ผิดวิธี | `X is not a function` `Cannot read properties of null` `Assignment to constant variable` | ตรวจ type ด้วย `typeof` ก่อนใช้งาน, ใช้ default value, เช็ค null/undefined ก่อนใช้ `.` |
| ReferenceError | Runtime (ตอนรัน) | อ้างอิงตัวแปรที่ไม่มีอยู่ใน scope ที่เข้าถึงได้ — สะกดผิด, TDZ, หรือใช้ตัวแปรนอก scope | `X is not defined` `Cannot access 'X' before initialization` | ประกาศตัวแปรก่อนใช้งาน, ตรวจการสะกดชื่อ, สนใจ scope, ใช้ `let`/`const` เพื่อให้ TDZ ช่วยเตือน |
| RangeError | Runtime (ตอนรัน) | ค่าอยู่นอกช่วงที่ยอมรับได้ — เกินขอบเขตของ method, array length ไม่ถูกต้อง, หรือ recursion ลึกเกิน | `Invalid array length` `Maximum call stack size exceeded` `digits argument must be between 0 and 100` | ตรวจสอบว่าค่าที่ส่งให้ method อยู่ในช่วงที่ยอมรับได้ — อ่าน documentation, ใช้ `if` ตรวจสอบก่อน, recursion ต้องมี base case |
สังเกตว่า SyntaxError เกิด**ก่อนรัน** — โค้ดทั้งไฟล์ไม่ทำงานเลย ส่วน RangeError, TypeError และ ReferenceError เกิด**ตอนรัน** — โค้ดก่อนหน้าทำงานได้ แต่หยุดที่บรรทัดที่เกิด error วิธีแยก RangeError จาก Error อื่นง่าย ๆ: - **SyntaxError**: syntax ผิด — โค้ดอ่านไม่รู้เรื่อง — เกิดก่อนรัน - **TypeError**: **type ผิด** — ตัวแปรมีอยู่แต่ใช้ผิด type → `"X is not a function"` - **ReferenceError**: **ตัวแปรไม่มีอยู่** — หาชื่อใน scope ไม่เจอ → `"X is not defined"` - **RangeError**: **ค่าเกินช่วง** — ตัวแปรมี type ถูก syntax ถูก แต่ค่าหลุดนอกขอบเขตที่รับได้ → `"Invalid array length"`, `"... must be between 0 and 100"` จำง่าย ๆ: Range = ช่วง/ขอบเขต — RangeError คือ error ที่บอกว่า "ค่าที่คุณให้มา มันมากไปหรือน้อยไป"
สรุป — RangeError
- **RangeError** คือ Error ที่เกิดเมื่อค่าที่ส่งให้ method หรือ operation อยู่นอกช่วงที่ยอมรับได้ — เกิดตอน **runtime** (ขณะโค้ดรัน) ไม่ใช่ตอน parse
- **สาเหตุหลัก 5 ประการ**: (1) `new Array(-1)` / `arr.length = -5` — array length ติดลบหรือ Infinity, (2) `toFixed()` digits เกิน 0-100, (3) `toPrecision()` precision เกิน 1-100, (4) `String.repeat()` count ติดลบหรือ Infinity, (5) recursion ที่ไม่มี base case → Maximum call stack size exceeded
- **จุดสังเกต**: error message มักบอกช่วงที่ถูกต้อง — `"digits argument must be between 0 and 100"`, `"Invalid count value"`, `"Maximum call stack size exceeded"` — อ่านแล้วรู้ทันทีว่าต้องแก้ค่าอย่างไร
- **Maximum call stack size exceeded**: สาเหตุพิเศษ — ไม่ได้เกิดจากการส่งค่าเกินช่วงให้ method แต่เกิดจากการเรียกฟังก์ชันซ้อนกันมากเกินไป — recursion ที่ไม่มี base case หรือ base case ไม่เคยถูกเข้าถึง
- **ป้องกันได้เกือบ 100%**: รู้ช่วงของ method ก่อนใช้, ตรวจสอบค่าด้วย `if` ก่อนเรียก method, recursion ต้องมี base case เสมอ, ใช้ loop แทน recursion สำหรับ iteration ธรรมดา, ใช้ `Number.isFinite()` ตรวจ Infinity
- **ต่างจาก Error อื่น**: SyntaxError = syntax ผิด (ก่อนรัน), TypeError = ใช้ค่าผิด type, ReferenceError = ตัวแปรไม่มีอยู่, RangeError = ค่าถูก type แต่อยู่นอกช่วง — จำง่าย ๆ: Range = ช่วง/ขอบเขต — ค่ามากไปหรือน้อยไป
- RangeError เป็น error ที่ตรงไปตรงมาที่สุด — error message บอกชัดว่าช่วงที่ถูกต้องคืออะไร — อ่าน message แล้วปรับค่าให้อยู่ในช่วง = แก้ได้ทันที
| สถานการณ์ | RangeError หรือไม่ | สาเหตุ / วิธีแก้ |
|---|---|---|
| `new Array(-1)` | ใช่ — RangeError | array length ต้อง >= 0 — ใช้ 0 หรือจำนวนเต็มบวก |
| `(1.23).toFixed(101)` | ใช่ — RangeError | toFixed รับ digits ได้ 0-100 — ใช้ค่าในช่วง 0-100 |
| `(1.23).toPrecision(0)` | ใช่ — RangeError | toPrecision รับ precision ได้ 1-100 — เริ่มที่ 1 ไม่ใช่ 0 |
| `"hello".repeat(-1)` | ใช่ — RangeError | repeat count ต้อง >= 0 และไม่ใช่ Infinity |
| recursion ไม่มี base case | ใช่ — RangeError | เพิ่ม base case หรือใช้ loop แทน |
| `null.name` | ไม่ใช่ — TypeError | null ไม่มี property — เช็คว่าไม่เป็น null ก่อนใช้ `.` |
| ใช้ตัวแปรที่ยังไม่ประกาศ | ไม่ใช่ — ReferenceError | ประกาศตัวแปรก่อนใช้งาน |
| ลืมปิดวงเล็บ `)` | ไม่ใช่ — SyntaxError | ตรวจวงเล็บให้ครบคู่ |