RangeError در جاوااسکریپت وقتی پرتاب می‌شود که مقداری که به یک تابع یا سازنده داده‌اید، از بازهٔ مجاز آن عملیات بیرون بزند؛ یعنی مرز بین «عددی که موتور می‌پذیرد» و «عددی که نمی‌پذیرد». این خطا در آرایه‌ها، اعداد، رشته‌ها و حتی بازگشت توابع بسیار رایج است و در ده‌ها پروندهٔ واقعی دیده‌ام ریشهٔ آن معمولاً یک فرض ضمنی در مورد بازه‌ها بوده. در این مقاله، با تجربه‌ام در دیباگ این خطا در Node، مرورگر و TypeScript همراه شما هستم.

RangeError چیست و از کجا می‌آید؟

RangeError یکی از کلاس‌های استثنای داخلی جاوااسکریپت است که وقتی پرتاب می‌شود که مقدار عددی یا مقداری که به یک تابع، سازنده یا عملیات داده شده، از بازهٔ مجاز آن بیرون بزند. تفاوت بنیادی این خطا با TypeError در همین «بازه» نهفته است: TypeError به نوع مقدار اشاره دارد و RangeError به دامنهٔ آن. مفهوم کلی این تفکیک در ویکی‌پدیا ذیل Exception handling توضیح داده شده است.

ساختار ارث‌بری RangeError در جاوااسکریپت ساده است:

Error
 └── RangeError

یعنی RangeError زیرکلاس مستقیم Error است و همین سادگی باعث می‌شود که در برخی مرورگرها و نسخه‌های Node، پیام‌های خطا تفاوت‌های ظریفی داشته باشند. اگر تازه با خطاهای جاوااسکریپت آشنا می‌شوید، مدیریت خطا در جاوااسکریپت را پیشنهاد می‌کنم، چون چارچوب گسترده‌تری از تمام کلاس‌های خطا را ارائه می‌دهد.

RangeError یک پیام مرزی است: «عددی که به من دادی، از بازه‌ای که می‌توانم تحمل کنم بیرون زده». برخلاف TypeError که نوع را زیر سؤال می‌برد، RangeError مقدار را قضاوت می‌کند.

نکتهٔ کلیدی این است که RangeError همیشه از سمت موتور جاوااسکریپت پرتاب نمی‌شود. گاهی کد شما (یا کتابخانه‌ای مثل React) به‌طور صریح این خطا را می‌سازد. یک نمونهٔ ساده از پرتاب دستی:

function setAge(age) {
    if (age < 0 || age > 150) {
        throw new RangeError("age must be between 0 and 150");
    }
    return age;
}

در این حالت، RangeError به‌عنوان یک قرارداد طراحی استفاده می‌شود: «اگر مقدار بیرون از بازهٔ مجاز بود، خطای مشخصی بده». این الگو در پروژه‌های بزرگ، دیباگ را چند برابر سریع‌تر می‌کند، چون نوع خطا خودش بخش بزرگی از تشخیص است.

درخت وارثت و تفاوت با TypeError

برای تشخیص درست، باید تفاوت RangeError با خطاهای نزدیکش را درک کنید. در جاوااسکریپت، هشت کلاس خطای داخلی وجود دارد که هر کدام یک حوزهٔ مشخص را پوشش می‌دهند:

کلاس خطامعنامثال
RangeErrorمقدار بیرون از بازهٔ مجازnew Array(-1)
TypeErrorنوع مقدار اشتباهundefined.foo
SyntaxErrorسینتکس نامعتبرJSON.parse("{bad}")
ReferenceErrorارجاع به متغیر تعریف‌نشدهunknownVar
URIErrorURI نامعتبرdecodeURIComponent("%")
EvalErrorخطای eval (منسوخ)—
AggregateErrorگروهی از خطاهاPromise.any
InternalErrorخطای داخلی موتور (غیراستاندارد)too much recursion

تفکیک RangeError از TypeError در نگاه اول ظریف به نظر می‌رسد ولی در عمل کلید تشخیص است. اگر مقدار عدد است ولی بازه‌اش اشتباه است (مثل -1 برای new Array)، این RangeError است. اگر مقدار اصلاً عدد نیست (مثل undefined یا "abc")، این TypeError است. برای درک عمیق‌تر این خانوادهٔ خطاها، مفاهیم پایه جاوااسکریپت مرجع مکمل خوبی است.

یک نکتهٔ ظریف: در برخی پیاده‌سازی‌های خاص، ممکن است خطای بیرون از بازه به‌جای RangeError به TypeError تبدیل شود، به‌ویژه وقتی نوع داده در فرآیند تبدیل مبهم باشد. این ناهمگونی، در پروژه‌هایی که روی چند مرورگر اجرا می‌شوند، منشأ باگ‌های ظریف است.

هفت بستری که RangeError در آن رخ می‌دهد

در جاوااسکریپت، RangeError در هفت بستر مشخص ظاهر می‌شود. شناختن این بسترها، نیمی از تشخیص است:

  1. سازندهٔ Array: وقتی طول آرایه منفی یا غیرصحیح یا بزرگ‌تر از 2^32 - 1 باشد.
  2. متدهای Number: toFixed، toPrecision و toExponential وقتی رقم درخواستی بیرون از بازهٔ مجاز باشد.
  3. متدهای String: repeat با مقدار منفی یا Infinity، و padStart با طول بیش از حد بزرگ.
  4. TypedArray: ساخت با طول غیرمجاز یا مقدار بیرون از بازه.
  5. بازگشت بی‌پایان: وقتی عمق فراخوانی از سقف call stack عبور کند.
  6. Intl: Intl.NumberFormat و مشابه‌ها وقتی locale یا گزینه‌های فرمت نامعتبر باشند.
  7. کد سفارشی: وقتی توسعه‌دهنده خودش این کلاس را برای اعتبارسنجی پرتاب می‌کند.

هر کدام از این بسترها، الگو و راه‌حل متفاوتی دارد. در ادامه، پرتکرارترین آن‌ها را با جزئیات بررسی می‌کنیم. اگر با مبانی آرایه و رشته در جاوااسکریپت آشنا نیستید، ابتدا متدهای آرایه در جاوااسکریپت را بخوانید.

RangeError در Array و TypedArray

رایج‌ترین بستر RangeError در جاوااسکریپت، سازندهٔ Array است. مهم است بدانید که new Array(n) با [n] تفاوت بنیادی دارد:

new Array(3)     // [<3 empty items>] — آرایه با طول 3
[3]              // [3] — آرایه با یک عنصر که مقدارش 3 است

new Array(-1)    // RangeError: Invalid array length
new Array(2.5)   // RangeError: Invalid array length
new Array(2 ** 32) // RangeError: Invalid array length

سه منشأ رایج این خطا در Array:

طول منفی یا غیرصحیح

اگر از ورودی کاربر یا محاسبهٔ ریاضی مقداری منفی یا اعشاری به Array بدهید، این خطا رخ می‌دهد:

// اشتباه
const n = parseInt(userInput); // اگر کاربر "-5" بدهد
const arr = new Array(n); // RangeError

// درست
const n = Math.max(0, Math.floor(Number(userInput)));
const arr = new Array(n);

نکتهٔ ظریف: parseInt("abc") مقدار NaN برمی‌گرداند و new Array(NaN) هم RangeError می‌دهد. همیشه قبل از ساخت آرایه، مقدار را اعتبارسنجی کنید.

طول بیش از حد بزرگ

حداکثر طول آرایه در جاوااسکریپت 2^32 - 1 یعنی ۴٬۲۹۴٬۹۶۷٬۲۹۵ است. اگر مقدار بزرگ‌تری بدهید، این خطا می‌آید:

const MAX = 2 ** 32;
new Array(MAX) // RangeError: Invalid array length

در عمل، حتی طول‌های نزدیک به این سقف هم به‌دلیل مصرف حافظه باعث crash می‌شوند. سقف عملی برای آرایه در V8 حدود ۱ میلیون تا ۱۰ میلیون است.

TypedArray و طول‌های خاص

در TypedArrayها مثل Uint8Array، این خطا وقتی رخ می‌دهد که طول یا offset با اندازهٔ buffer سازگار نباشد:

const buffer = new ArrayBuffer(8);
new Uint16Array(buffer, 0, 10); // RangeError: Invalid typed array length

منطق این خطا: هر عنصر Uint16Array دو بایت اشغال می‌کند، پس ۱۰ عنصر نیاز به ۲۰ بایت دارد ولی buffer فقط ۸ بایت دارد. این نوع خطا در پروژه‌های پردازش باینری (مثل WebSocket یا FileReader) زیاد رخ می‌دهد.

برای آشنایی با مباحث پیشرفته‌تر آرایه، آموزش ES6 در جاوااسکریپت نکات مکمل را ارائه می‌دهد.

RangeError در Number و toFixed

متدهای Number، پارامتر digits می‌پذیرند که بازهٔ مشخصی دارد. بیرون از این بازه، RangeError پرتاب می‌شود:

(123.456).toFixed(2)     // "123.46"
(123.456).toFixed(100)   // RangeError: toFixed() digits argument must be between 0 and 100
(123.456).toPrecision(0) // RangeError: toPrecision() argument must be between 1 and 100
(123.456).toExponential(-1) // RangeError

بازه‌های مجاز:

متدبازهٔ مجاز digits
toFixed۰ تا ۱۰۰
toPrecision۱ تا ۱۰۰
toExponential۰ تا ۱۰۰

الگوی امن:

function safeToFixed(value, digits = 2) {
    const n = Math.max(0, Math.min(100, Math.floor(digits)));
    return Number(value).toFixed(n);
}

نکتهٔ ظریف: در کتابخانه‌هایی مثل numeral.js و decimal.js، ممکن است بازهٔ مجاز متفاوت باشد. همیشه مستندات را بررسی کنید.

مبحث دیگر، Number.prototype.toLocaleString با گزینه‌های پیشرفته است که در بخش Intl بررسی می‌کنیم.

RangeError: Maximum call stack size exceeded

این یکی از پرتکرارترین پیام‌های RangeError است و ریشه‌اش دقیقاً همان چیزی است که در پایتون به‌عنوان خطای RecursionError در پایتون بررسی کردم. اما در جاوااسکریپت، شرایط کمی متفاوت است.

علت فنی

هر فراخوانی تابع در جاوااسکریپت، یک فریم روی call stack اضافه می‌کند. اندازهٔ این stack بسته به موتور جاوااسکریپت متفاوت است:

موتورسقف تقریبی فریم‌ها
V8 (Chrome، Node)۱۰٬۰۰۰ تا ۱۵٬۰۰۰
SpiderMonkey (Firefox)۱۰٬۰۰۰ تا ۲۰٬۰۰۰
JavaScriptCore (Safari)۳۰٬۰۰۰ تا ۴۰٬۰۰۰

وقتی این سقف پر شود، موتور این خطا را پرتاب می‌کند. در Node، می‌توانید با --stack-size این مقدار را تغییر دهید، ولی راه‌حل درست معمولاً بازنویسی الگوریتم است.

سناریوهای رایج

سه سناریوی اصلی که به این خطا منجر می‌شوند:

// 1. بازگشت بدون شرط پایه
function countdown(n) {
    console.log(n);
    countdown(n - 1); // هرگز متوقف نمی‌شود
}

// 2. بازگشت متقابل
function isEven(n) {
    if (n === 0) return true;
    return isOdd(n - 1);
}
function isOdd(n) {
    if (n === 0) return false;
    return isEven(n - 1);
}
isEven(100000); // RangeError

// 3. JSON.stringify روی ساختار circular
const obj = {};
obj.self = obj;
JSON.stringify(obj); // TypeError: Converting circular structure to JSON — نه RangeError، ولی گاهی باعث خطای دیگر می‌شود

توجه: بسته به عمق و ساختار داده، ممکن است پیام خطای دقیق متفاوت باشد. در V8 معمولاً RangeError: Maximum call stack size exceeded و در برخی نسخه‌های قدیمی InternalError دیده می‌شود.

تبدیل به حلقه

راه‌حل استاندارد، تبدیل بازگشت به حلقه است:

// بازگشتی
function factorial(n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

// iterative
function factorial(n) {
    let result = 1;
    for (let i = 2; i <= n; i++) result *= i;
    return result;
}

برای پیمایش درخت و گراف، از یک stack یا صف صریح استفاده کنید:

function traverseIterative(root) {
    const stack = [root];
    while (stack.length) {
        const node = stack.pop();
        process(node);
        if (node.left) stack.push(node.left);
        if (node.right) stack.push(node.right);
    }
}

این الگو در پروژه‌های فرانت‌اند که با ساختارهای تودرتوی DOM یا JSON کار می‌کنند، بسیار مفید است. برای مطالعهٔ بیشتر، دستکاری DOM در جاوااسکریپت نمونه‌های عملی پیمایش درخت را نشان می‌دهد.

RangeError در String و Intl

متدهای رشته در جاوااسکریپت هم پارامترهای عددی می‌پذیرند که بازهٔ مجاز دارند. دو مورد از پرتکرارترین‌ها:

String.prototype.repeat

"abc".repeat(3)     // "abcabcabc"
"abc".repeat(0)     // ""
"abc".repeat(-1)    // RangeError: Invalid count value
"abc".repeat(Infinity) // RangeError

این خطا در الگوهای تولید داده آزمایشی (مثل padding با کاراکتر خاص) بسیار رخ می‌دهد. راه‌حل: اعتبارسنجی ورودی قبل از repeat.

String.prototype.padStart و padEnd

"5".padStart(3, "0")     // "005"
"5".padStart(-1, "0")    // "5" — بدون خطا، مقدار صفر در نظر گرفته می‌شود
"5".padStart(2 ** 30, "0") // RangeError: Invalid string length

نکته: padStart با طول منفی خطا نمی‌دهد ولی با طول بیش از حد بزرگ، RangeError می‌دهد. سقف طول رشته در V8 حدود 2^29 - 24 بایت است.

Intl و locale های نامعتبر

در Intl.NumberFormat، بعضی locale‌های خاص و optionهای invalid ممکن است خطا بدهند:

new Intl.NumberFormat("fa-IR", { minimumFractionDigits: -1 });
// RangeError: minimumFractionDigits value is out of range

بازه‌های مجاز در Intl.NumberFormat بسیار محدودند: minimumFractionDigits و maximumFractionDigits باید بین ۰ تا ۲۰ باشند. هر مقدار بیرون از این بازه، این خطا را می‌دهد.

در Intl، بازه‌های مجاز به‌صورت دقیق در spec تعریف شده‌اند و بین مرورگرها ناهمگونی وجود ندارد. اگر RangeError می‌بینید، احتمالاً گزینه‌های فرمت را از ورودی کاربر بدون اعتبارسنجی گرفته‌اید.

برای مطالعات بیشتر در حوزهٔ رفتارهای خاص جاوااسکریپت مدرن، async و await در جاوااسکریپت نکات مرتبط را ارائه می‌دهد.

سناریوهای واقعی در پروژه‌ها

در طول سال‌ها کار با جاوااسکریپت، RangeError را در این هفت الگو دیده‌ام. شناختن هر الگو، تشخیص را چند برابر سریع‌تر می‌کند.

سناریوی اول: تقسیم صفحه‌بندی با مقسوم‌علیه صفر

در محاسبهٔ تعداد صفحات، اگر itemsPerPage صفر باشد، نتیجه Infinity می‌شود و سپس در new Array خطا می‌دهد:

// اشتباه
const totalPages = Math.ceil(items.length / perPage);
const pages = new Array(totalPages); // اگر perPage=0 → RangeError

// درست
const safePerPage = Math.max(1, perPage);
const totalPages = Math.ceil(items.length / safePerPage);

سناریوی دوم: پارس ورودی کاربر

ورودی کاربر همیشه غیرقابل‌اعتماد است:

// اشتباه
const size = parseInt(input.value);
const buf = new Uint8Array(size); // RangeError اگر مقدار منفی یا خیلی بزرگ

// درست
const raw = parseInt(input.value, 10);
if (!Number.isFinite(raw) || raw < 0 || raw > 1_000_000) {
    throw new RangeError("size must be between 0 and 1,000,000");
}
const buf = new Uint8Array(raw);

این الگو در پروژه‌های وب که با FileReader یا WebSocket کار می‌کنند، بسیار مهم است. برای مطالعهٔ بیشتر، Fetch API در جاوااسکریپت نکات مکمل را ارائه می‌دهد.

سناریوی سوم: تکرار رشته برای نمایش progress

در ساخت نوار پیشرفت با کاراکترهای تکرار‌شده، ممکن است مقدار محاسبه‌شده منفی شود:

// اشتباه
const percent = Math.floor((current / total) * 100);
const bar = "█".repeat(percent); // اگر current > total → منفی می‌شود

// درست
const clamped = Math.max(0, Math.min(100, Math.floor((current / total) * 100)));
const bar = "█".repeat(clamped);

سناریوی چهارم: بازگشت در deep clone

پیاده‌سازی دستی deep clone، اگر محافظت از circular reference نداشته باشد، به این خطا می‌خورد:

// اشتباه
function deepClone(obj) {
    const clone = {};
    for (const key in obj) {
        clone[key] = typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key];
    }
    return clone;
}

// درست: استفاده از structuredClone یا محافظت
const clone = structuredClone(obj);

نکته: structuredClone از Node ۱۷ و تمام مرورگرهای مدرن پشتیبانی می‌شود و به‌طور داخلی circular reference را مدیریت می‌کند.

سناریوی پنجم: backoff نمایی بدون سقف

در retry logic، اگر backoff نمایی بدون سقف باشد، ممکن است به عدد Infinity برسد و در ادامه در setTimeout خطا بدهد:

// اشتباه
const delay = base ** attempt; // اگر attempt بزرگ باشد → Infinity
setTimeout(retry, delay);

// درست
const delay = Math.min(base ** attempt, 30_000);

سناریوی ششم: formatter تاریخ با روز اشتباه

در محاسبهٔ تاریخ، اگر ماه یا روز بیرون از بازه باشد، خطا می‌دهد:

new Date(2025, 13, 1); // ماه 13 قبول نمی‌شود؛ به 2026 منتقل می‌شود
new Date(2025, 0, 1).toLocaleDateString("fa-IR", { day: "numeric" }); // OK

// ولی:
new Intl.DateTimeFormat("fa-IR", { weekday: -1 }); // RangeError

سناریوی هفتم: پردازش تصویر و canvas

در APIهای Canvas، ساخت ImageData با ابعاد اشتباه، این خطا را می‌دهد:

// اشتباه
const data = new ImageData(-100, -100); // RangeError

// درست
const data = new ImageData(800, 600);

سناریوهای مشابه در بخش‌های دیگر جاوااسکریپت در ایونت‌ها در جاوااسکریپت هم پوشش داده شده است.

روش تشخیص در پنج گام

در برخورد با RangeError، پروتکل زیر را در پروژه‌های خودم اجرا می‌کنم. در بیشتر پرونده‌ها، گام دوم یا سوم مقصر را روشن می‌کند.

گام اول: خواندن دقیق stack trace

پیام خطا و stack trace، اطلاعات بسیار مفیدی می‌دهند:

RangeError: Invalid array length
    at buildList (app.js:45:18)
    at renderUsers (app.js:88:12)
    at loadUsers (app.js:120:8)
    at async main (app.js:150:5)

سه چیز در این trace مهم است: نام کلاس خطا، فایل و شمارهٔ خط، و زنجیرهٔ فراخوانی. از پایین به بالا بخوانید تا نقطهٔ شروع خطا را پیدا کنید.

گام دوم: بررسی مقادیر ورودی

در همان خط، مقدار متغیرهای ورودی را لاگ کنید:

function buildList(items, perPage) {
    console.log({ itemsCount: items.length, perPage });
    const totalPages = Math.ceil(items.length / perPage);
    return new Array(totalPages); // اینجا RangeError می‌دهد
}

در ۸۰٪ موارد، مشکل با همین یک لاگ روشن می‌شود. مثلاً perPage=0 باعث Infinity و سپس RangeError می‌شود.

گام سوم: استفاده از DevTools و breakpoint

در Chrome DevTools، روی خط خطا کلیک راست کنید و «Add conditional breakpoint» بزنید. شرط را روی !Number.isFinite(value) یا مشابه بگذارید تا فقط وقتی مقدار غیرمعتبر است متوقف شود:

// در DevTools، conditional breakpoint:
!Number.isInteger(totalPages) || totalPages < 0

این تکنیک، در پروژه‌های بزرگ که خطا در حلقه‌های طولانی رخ می‌دهد، بسیار مفید است.

گام چهارم: بررسی بازگشت با stack overflow tracker

برای خطای Maximum call stack size exceeded، در Chrome می‌توانید از پنل Performance استفاده کنید:

// در DevTools، یک اسنیپت برای شمارش عمق بازگشت:
function withDepth(fn) {
    let depth = 0;
    return function (...args) {
        depth++;
        if (depth > 5000) {
            console.error("depth exceeded:", new Error().stack);
            depth = 0;
            throw new RangeError("manual depth limit");
        }
        try {
            return fn.apply(this, args);
        } finally {
            depth--;
        }
    };
}

این wrapper را در محیط توسعه روی تابع مشکوک بگذارید تا نقطهٔ دقیق بازگشت بی‌پایان مشخص شود.

گام پنجم: تست در محیط‌های مختلف

یکی از دام‌های RangeError، ناهمگونی مرورگرهاست. یک کد ممکن است در Chrome بی‌خطا باشد ولی در Safari یا Node خطا بدهد. همیشه در محیط‌های زیر تست کنید:

  • Chrome و Firefox و Safari (حداقل یک نسخهٔ فعلی)
  • Node.js (دو نسخهٔ LTS)
  • موبایل (Android و iOS)

اگر با Node کار می‌کنید و خطا در حین استریم داده رخ می‌دهد، بهینه‌سازی عملکرد جاوااسکریپت نکات مرتبط را ارائه می‌دهد.

الگوهای امن و پیشگیری

پس از تشخیص، الگوهای زیر را در پروژه‌های خودم رعایت می‌کنم. این شش الگو، بیشترین اثر را در کاهش RangeError دارند.

الگوی اول: اعتبارسنجی ورودی در مرزها

هرجا داده از یک منبع خارجی (کاربر، API، فایل) وارد می‌شود، بازهٔ مجاز را بررسی کنید:

function validateNumber(value, min, max, name) {
    const n = Number(value);
    if (!Number.isFinite(n)) {
        throw new TypeError(`${name} must be a finite number`);
    }
    if (n < min || n > max) {
        throw new RangeError(`${name} must be between ${min} and ${max}, got ${n}`);
    }
    return n;
}

این تابع را در همهٔ مرزهای ورودی پروژه استفاده کنید. مزیت: هم از RangeError جلوگیری می‌کند و هم پیام خطا را معنادار می‌سازد.

الگوی دوم: استفاده از clamp برای مقادیر عددی

function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
}

const size = clamp(userInput, 0, 1_000_000);
const arr = new Array(size);

الگوی clamp در محاسبات گرافیکی، انیمیشن و پارس ورودی بسیار مفید است.

الگوی سوم: بازگشت محدود شده

function safeRecursive(fn, maxDepth = 1000) {
    let depth = 0;
    return function recurse(...args) {
        if (depth++ > maxDepth) {
            throw new RangeError(`recursion depth exceeded ${maxDepth}`);
        }
        try {
            return fn.call(this, recurse, ...args);
        } finally {
            depth--;
        }
    };
}

const factorial = safeRecursive(function (recurse, n) {
    return n <= 1 ? 1 : n * recurse(n - 1);
});

این الگو، خطای واقعی را در عمق مشخص و با پیام معنادار پرتاب می‌کند، نه با crash موتور.

الگوی چهارم: بررسی circular reference قبل از پیمایش

function traverseSafe(obj, visited = new WeakSet()) {
    if (visited.has(obj)) return;
    visited.add(obj);
    for (const key in obj) {
        if (typeof obj[key] === "object" && obj[key] !== null) {
            traverseSafe(obj[key], visited);
        }
    }
}

استفاده از WeakSet به‌جای Set این مزیت را دارد که نگه‌داشتن reference‌ها باعث نشت حافظه نمی‌شود.

الگوی پنجم: استفاده از iterators به‌جای بازگشت

در پیمایش درخت‌های عمیق، iteratorها هم خوانا هستند و هم بدون محدودیت عمق:

function* walk(node) {
    const stack = [node];
    while (stack.length) {
        const current = stack.pop();
        yield current;
        if (current.children) {
            for (const child of current.children) stack.push(child);
        }
    }
}

for (const node of walk(root)) {
    process(node);
}

الگوی ششم: مدیریت خطا در سطح ماژول

در بالاترین سطح برنامه، یک error handler کلی داشته باشید که RangeError را جدا مدیریت کند:

window.addEventListener("error", (event) => {
    if (event.error instanceof RangeError) {
        reportToMonitoring(event.error, { type: "range" });
    }
});

در Node:

process.on("uncaughtException", (error) => {
    if (error instanceof RangeError) {
        console.error("Range error occurred:", error.message);
        process.exit(1);
    }
    // سایر خطاها
});

این الگو در سرویس‌های production بسیار مفید است. برای مطالعهٔ بیشتر دربارهٔ مدیریت خطا در برنامه‌های واقعی، شی گرایی در جاوااسکریپت نکات مرتبط را ارائه می‌دهد.

RangeError در React، Vue و Node

هر فریم‌ورک، رفتار خاص خود را با RangeError دارد. شناخت این رفتارها، دیباگ در محیط‌های واقعی را سریع‌تر می‌کند.

React و React Router

در React، بعضی از خطاهای RangeError از خود React می‌آیند. مثلاً در useState با مقدار اولیهٔ نامعتبر:

// در React 18
const [data, setData] = useState(undefined);
// گاهی در فرآیند hydrate، باعث خطای ابعاد می‌شود

در React Router v6، اگر پارامتر مسیر id با ساختار موردانتظار هم‌خوانی نداشته باشد، ممکن است RangeError ببینید. راه‌حل: همیشه پارامترها را در لودر یا کامپوننت اعتبارسنجی کنید.

Vue و Reactivity

در Vue، اگر از v-for روی آرایه‌ای با طول نامعتبر استفاده کنید، ممکن است خطا ببینید:

// در Vue 3
const items = ref(new Array(-1)); // RangeError

راه‌حل: مقدار اولیهٔ آرایه را با [] تنظیم کنید و سپس با مقادیر معتبر پر کنید.

Node.js و Buffer

در Node، Buffer.alloc بازهٔ مجاز مشخصی دارد:

Buffer.alloc(-1);         // RangeError
Buffer.alloc(2 ** 32);    // RangeError: buffer size too large

// راه‌حل
const size = Math.max(0, Math.min(2 ** 31 - 1, requestedSize));
const buf = Buffer.alloc(size);

این خطا در سرورهایی که فایل‌های بزرگ را آپلود می‌کنند، بسیار شایع است. همیشه سقف اندازه را در سطح API تعیین کنید.

Node.js و child_process

در child_process.spawn، اگر maxBuffer از حد عبور کند، خطای ERR_CHILD_PROCESS_STDIO_MAXBUFFER می‌گیرید که زیرکلاس RangeError است:

const { spawn } = require("child_process");

spawn("ls", ["-la"], {
    maxBuffer: 1024 * 1024 * 10, // 10 MB
});

راه‌حل: مقدار maxBuffer را متناسب با خروجی مورد انتظار تنظیم کنید یا از استریم به‌جای buffer استفاده کنید.

TypeScript و type safety

در TypeScript، RangeError به‌طور مستقیم توسط کامپایلر چک نمی‌شود، ولی می‌توانید با branded types الگوهای امن بسازید:

type ArrayLength = number & { readonly __brand: "ArrayLength" };

function asArrayLength(n: number): ArrayLength {
    if (!Number.isInteger(n) || n < 0 || n > 2 ** 32 - 1) {
        throw new RangeError("invalid array length");
    }
    return n as ArrayLength;
}

این الگو، در سطح type system، از خطاهای ناشی از RangeError جلوگیری می‌کند. برای مطالعهٔ بیشتر، Promise در جاوااسکریپت نکات مرتبط با async را ارائه می‌دهد.

پرسش‌های پرتکرار درباره RangeError

این بخش، پرسش‌هایی را پوشش می‌دهد که در جلسات مشاوره و انجمن‌های فنی بیشترین تکرار را داشته‌اند. پاسخ‌ها به‌شکلی نوشته شده‌اند که برای جستجوهای مستقیم و دستیارهای هوش مصنوعی به‌عنوان پاسخ معتبر قابل استخراج باشند.

تفاوت RangeError و TypeError در جاوااسکریپت چیست؟

RangeError نشان می‌دهد که مقدار از بازهٔ مجاز بیرون زده است ولی نوع آن درست بوده. TypeError نشان می‌دهد که نوع مقدار اشتباه است. برای نمونه، new Array(-1) یک RangeError است (عدد است ولی منفی)، ولی new Array("abc") یک TypeError نیست چون Array با یک آرگومان عددی، طول می‌سازد و "abc" به عنوان طول تبدیل می‌شود. برای تفکیک دقیق، مقدار را در سطح ورودی اعتبارسنجی کنید.

چرا «Maximum call stack size exceeded» یک RangeError است؟

چون عمق بازگشت از بازهٔ مجاز call stack بیرون زده است. موتور جاوااسکریپت، call stack را در حافظه‌ای محدود ذخیره می‌کند و وقتی این حافظه پر شود، دیگر فضایی برای فریم جدید نیست. این خطا زیرکلاس RangeError است چون ماهیت آن «خارج شدن از بازه» است، نه «نوع اشتباه». مفهوم call stack در ویکی‌پدیا ذیل Call stack توضیح داده شده است.

آیا RangeError در همه مرورگرها یکسان است؟

کلاس و رفتار اصلی یکسان است، ولی پیام‌های متنی متفاوتند. مثلاً در Chrome پیام "Invalid array length" و در Firefox پیام "invalid array length" یا "RangeError: invalid array length" دیده می‌شود. برای پورتال‌های production، هرگز بر اساس متن پیام کد ننویسید؛ از instanceof RangeError استفاده کنید.

چگونه RangeError را در try/catch مدیریت کنیم؟

الگوی درست: تفکیک دقیق کلاس خطا:

try {
    riskyOperation();
} catch (error) {
    if (error instanceof RangeError) {
        console.error("range error:", error.message);
    } else if (error instanceof TypeError) {
        console.error("type error:", error.message);
    } else {
        throw error;
    }
}

نکته: همیشه else آخر داشته باشید که خطاهای ناشناخته را دوباره پرتاب کند تا در فرآیند دیباگ گم نشوند.

چرا RangeError در Node بعد از استریم داده رخ می‌دهد؟

در Node، بعضی از Readable streamها اگر highWaterMark یا بافر داخلی‌شان از حد مجاز عبور کند، خطای ERR_OUT_OF_RANGE می‌دهند که زیرکلاس RangeError است. راه‌حل: اندازهٔ chunk را کوچک کنید یا از pipeline استفاده کنید که مدیریت فشار (backpressure) را خودکار انجام می‌دهد.

آیا با افزایش stack size در Node می‌توان RangeError را دور زد؟

فنی بله، با node --stack-size=2000 app.js می‌توانید سقف را افزایش دهید، ولی این راه‌حل سطحی است. اگر الگوریتم شما ذاتاً به بازگشت بی‌پایان نیاز دارد، افزایش stack فقط زمان crash را عقب می‌اندازد. راه‌حل بلندمدت، تبدیل بازگشت به حلقه است.

آیا structuredClone هم RangeError می‌دهد؟

بله، در مواردی که ساختار داده خیلی بزرگ باشد یا عمق بازگشت داخلی‌اش از حد عبور کند. برای داده‌های چند‌مگابایتی، structuredClone سریع‌تر و امن‌تر از پیاده‌سازی دستی است، ولی برای داده‌های چندگیگابایتی باید از استریم استفاده کنید.

چطور بفهمم RangeError از کد من یا از کتابخانه می‌آید؟

در stack trace، به فریم‌های پایینی نگاه کنید. اگر اولین فریم (پایین‌ترین) داخل فایل‌های node_modules است، خطا از کتابخانه می‌آید. اگر فایل شما بالاترین فریم است ولی فراخوانی از کتابخانه می‌آید، مسئله احتمالاً در ورودی‌هایی است که به کتابخانه می‌دهید.

آیا می‌توان RangeError را داخل Promise مدیریت کرد؟

بله، با .catch() یا try/catch داخل async:

async function fetchData() {
    try {
        return await fetch(url).then((r) => r.json());
    } catch (error) {
        if (error instanceof RangeError) {
            console.warn("invalid range in response");
        }
        throw error;
    }
}

نکته: در Promise chain، خطاهایی که در سطح microtask رخ می‌دهند، ممکن است به unhandledRejection تبدیل شوند. همیشه یک error handler کلی در سطح برنامه داشته باشید.

چگونه RangeError را در تست‌ها پوشش دهیم؟

با Jest یا Vitest:

expect(() => new Array(-1)).toThrow(RangeError);
expect(() => new Array(-1)).toThrow("Invalid array length");

برای تست دقیق‌تر، از toThrowErrorMatchingInlineSnapshot یا toThrowErrorMatchingSnapshot استفاده کنید.

برای مطالعات مکمل دربارهٔ خطاهای جاوااسکریپت، آموزش جاوااسکریپت از صفر مرجع جامعی برای شروع است.

درس‌هایی که این خطا به معماری کد من اضافه کرد

RangeError بیش از آنکه یک خطای فنی باشد، یک «درس طراحی» است. سه اصلی که پس از سال‌ها کار با آن، در معماری کد خودم رعایت می‌کنم:

نخست، بازه‌ها را در سطح API صریح کنید، نه در پیاده‌سازی. هر تابعی که مقدار عددی می‌پذیرد، باید بازهٔ مجاز را در امضای خود یا در مستندات ذکر کند. وقتی بازه در سطح API صریح باشد، اعتبارسنجی به‌طور طبیعی در مرز انجام می‌شود، نه در لایه‌های داخلی. تجربه‌ام این است که تیم‌هایی که این عادت را دارند، تقریباً هیچ‌وقت با RangeError در تولید غافلگیر نمی‌شوند.

دوم، بازگشت را مسئله‌ای طراحی کنید، نه ابزار پیش‌فرض. در ۸۰٪ مواردی که RangeError ناشی از بازگشت است، الگوریتم با یک حلقه ساده قابل بازنویسی است. قاعده‌ام این است: اگر عمق بازگشت تضمین‌شده لگاریتمی نیست، از همان ابتدا iterative بنویسید. هزینهٔ اضافه، صفر است؛ ولی سود آن در محیط تولید بی‌نهایت است.

سوم، خطاها را با context معنادار پرتاب کنید. هرجا خودتان RangeError را پرتاب می‌کنید، پیام را با تمام اطلاعات لازم بنویسید: نام فیلد، مقدار فعلی، بازهٔ مجاز. این پیام، سه ماه بعد وقتی خطا در لاگ ظاهر شود، نجات‌دهنده است:

throw new RangeError(
    `itemsPerPage must be between 1 and 1000, got ${value}`
);

در پایان، اگر در پروژه‌ای با حالت خاصی از RangeError برخورد کردید که این‌جا پوشش داده نشده — مثلاً در ترکیب با WebAssembly، Intl پیشرفته، یا در محیط‌های خاص مرورگرهای قدیمی — تجربه‌تان را در دیدگاه‌ها بنویسید. به‌ویژه اگر راه‌حلی متفاوت از رویکردهای معمول پیدا کرده‌اید که می‌تواند برای خوانندهٔ بعدی ارزشمند باشد. 🧭