باگها و خطاها اجزای جدانشدنی در برنامهنویسی هستند. در این مقاله به بررسی این که چگونه خطاها را با استفاده از try/catch در جاوااسکریپت مدیریت کنیم، میپردازیم.
دستور try/catch اساسا برای رسیدگی به خطاها در جاوااسکریپت استفاده میشود. زمانیکه بخواهیم خطاهای کد را مدیریت کنیم از این دستور میتوانیم استفاده کنیم.
یک دستور try امکان آزمایش یک بلاک از کد را جهت بررسی وجود خطا، فراهم میکند.
دستور catch مدیریت خطاها را برای ما ممکن میسازد.
برای مثال:
try{ //... }catch(e){ //... }
در واقع دستور try/catch به شکل فوق ساخته میشود. کد خود را در بلاک try قرار میدهیم و در صورت بروز خطا، بلافاصله جاوااسکریپت کنترل را به دستور catch میدهد. در این مثال، بلاک catch فقط ما را از وجود خطا مطلع میکند.
تمام خطاهای ممکن در جاوااسکریپت، در واقع آبجکتهایی شامل دو ویژگی نام (برای مثال، Error، syntaxError) و پیام خطا هستند. برای همین هنگامی که در بلاک catch از دستور alert e استفاده میکنیم پیام خطای ReferenceError: getData is not defined را دریافت میکنیم.
مشابه تمام آبجکتهای جاوااسکریپت میتوان به شکل e.name (نام خطا) و e.message (پیام خطای اتفاق افتاده) به مقادیر آن دسترسی داشت.
یکی از مزایای استفاده از دستور try/catch این است که میتوانیم خطای سفارشی ساخته شدهی خود را نمایش دهیم که به آن throw error گفته میشود.
در مواقعی که نمیخواهیم پیام خطای تولید شده توسط خود جاوااسکریپت نمایش داده شود، میتوانیم با دستور throw پیام خطای مورد نظر خود را استفاده کنیم. خطا میتواند از نوع رشته (string)، مقادیر true یا false و آبجکت باشد. در صورت بروز خطا دستور catch پیامی که تعیین کردهایم را نمایش خواهد داد.
let num =prompt("insert a number greater than 30 but less than 40") try { if(isNaN(num)) throw "Not a number!" else if (num>40) throw "Did you even read the instructions ಠ︵ಠ, less than 40" else if (num <= 30) throw "Greater than 30" }catch(e){ alert(e) }
علاوه بر این روش میتوانیم از constructor error جاوااسکریپت هم استفاده کنیم.
به طور کلی در جاوااسکریپت میتوانیم خطاهای متفاوتی داشته باشیم:
EvalError: خطایی که در تابع eval() اتفاق میافتد.
RangeError: زمانی که عددی خارج از محدوده مشخص داشته باشیم، برای مثال ۱٫toPrecision(500).toPrecision تابعی است که برای اعداد یک مقدار دهدهی برمیگرداند مثل ۱٫۰۰۰ و لزوما هر عدد تنها یک مقدار معادل خواهد داشت.
ReferenceError: استفاده از متغیری که تعریف نشده است.
syntaxError: هنگام ارزیابی کدی که دارای خطای نگارشی (syntax error) باشد.
TypeError: اگر از مقداری استفاده کنید که خارج از محدودهی نوع مورد انتظار است برای مثال ۱٫toUpperCase().
با این همه میتوانیم مثلا به شکل throw new Error(“Hi there”) یک خطای جدید با نام Error و پیام خطای Hi there را ایجاد و استفاده کنیم. علاوه بر این میتوانیم error constructor سفارشی خود را ایجاد کنیم. برای مثال:
function CustomError(message){ this.value ="customError"; this.message=message; }
و از آن با استفاده از دستوری به شکل زیر هر جا نیاز بود استفاده کنیم:
throw new CustomError("data is not defined")
تا این جا به آشنایی با بلاک try/catch و این که چگونه از خراب شدن اسکریپت ما جلوگیری میکند، پرداختیم.
مثال زیر را در نظر بگیرید:
try{ console.log({{}}) }catch(e){ alert(e.message) } console.log("This should run after the logged details")
اگر کد فوق را امتحان کنیم حتی با وجود دستور try، خواهیم دید که کار نمیکند. دلیل این اتفاق وجود دو دسته کلی از خطاها در جاوااسکریپت است: parse-time error و runtime errors یا exceptions.
Parse-time error ها خطاهایی هستند که داخل کد اتفاق میافتند و دلیل اصلی آنها این است که مفسر کد، قسمتی از کد را متوجه نمیشود.
برای مثال در کد فوق، جاوااسکریپت منظور ما را از نوشتن {{}} متوجه نمیشود و درنتیجه دستور try/catch در اینجا کار نمیکند.
از طرفی خطاهای runtime خطاهایی هستند که در کد معتبر اتفاق میافتند و توسط بلاکهای try/catch قابل شناسایی هستند.
try{ y=x+7 } catch(e){ alert("x is not defined") } alert("try catch will handle this to prevent your code from breaking")
در این کد که کد معتبری محسوب میشود بلاک try/catch به خوبی خطا را کنترل خواهد کرد.
هنگام استفاده از دستور finally بدون توجه به نتیجهی بلاک try/catch (خطا یا عدم وجود خطا)، کدی که در دستور finally قرار گرفته اجرا میشود.
let data=prompt("name") try{ if(data==="") throw new Error("data is empty") else alert(`Hi ${data} how do you do today`) } catch(e){ alert(e) } finally { alert("welcome to the try catch article") }
از مزایای نوشتن بلاک های try به صورت تودرتو این است که می توانیم از یک دستور catch برای چندین دستور try استفاده کنیم. میتوانیم برای هر بلاک try یک دستور catch به صورت زیر بنویسیم:
try { try { throw new Error('Error'); } catch(e){ console.log(e) } finally { console.log('finally'); } } catch (err) { console.log('Error '+err); }
در این حالت، هیچ خطایی در بلاک try خارجی وجود نخواهد داشت چون هیچ مشکلی در آن وجود ندارد. خطا در بلاک try داخلی وجود دارد که با استفاده از دستور catch کنترل میشود. کد زیر را در نظر بگیرید:
try { try { throw new Error('inner catch error'); } finally { console.log('finally'); } } catch (err) { console.log(err); }
کد فوق به شکل متفاوتی کار می کند: خطا در بلاک try داخلی که دارای دستور catch نیست و به جای آن دستور finally وجود دارد، اتفاق میافتد.
دستور finally در try داخلی قطعا کار میکند. چون همانطور که گفتیم بدون توجه به این که در بلاک try/catch چه اتفاقی میافتد دستور finally کار خواهد کرد. اما با این که در بلاک try خارجی خطایی رخ نمیدهد، کنترل خطا به دستور catch این بلاک داده می شود تا خطا را ثبت کند. همین طور از پیام خطایی که در دستور try داخلی ایجاد کردیم استفاده میکند. به این دلیل که خطا متعلق به آن بلاک است.
برای درک بهتر میتوانید در کد زیر با کامنت کردن catch داخلی، نتیجه را مشاهده کنید:
try { try { throw new Error('inner catch error'); } catch(e){ //comment this catch out console.log(e) } finally { console.log('finally'); } throw new Error("outer catch error") } catch (err) { console.log(err); }