2 هفته قبل

بدون دیدگاه

error handling in javascript

Error handling در جاوااسکریپت (قسمت اول)

ارور‌ها(خطاها) بخشی از زندگی یه توسعه دهنده محسوب میشن که نمیتونیم ازشون فرار کنیم یا اونا رو مخفی کنیم. تنها کاری که می‌تونیم انجام بدیم کنترل کردن و مدیریت اونهاست. توی این مقاله درمورد کنترل کردن خطا(error handling) در node.js میگیم.

چی میشه وقتی موقع اجرای برنامه‌ای که نوشتیم یهو این عبارت بیاد: uncaughtException 🙄😑 و برنامه متوقف و خراب بشه؟ :/ مطمئنا هیچکی خوشش نمیاد :/ خب پس بریم چندتا راه رو مرور کنیم که ارور‌ها رو کنترل کنیم، کد تمیزتری بنویسیم و مدت زمان پیدا کردن ارور‌ها رو کم کنیم و در نهایت تجربه کاربری بهتری هم ارائه بدیم 🙂

Error Handling در جاوااسکریپت چی هست؟

در حالت عادی ممکنه برطرف کردن ارور‌ها سخت بنظر بیاد. مخصوصا با توجه به بعضی ارور‌هایی که سر و تهش هم معلوم نیس. اما اگه بخوایم منصف باشیم، وقتی یه error handling درستی داشته باشیم مدیریت خطاهامون اونقد هم سخت نیس. از طرفی مدیریت خطاها بسته به اندازه پروژه و هدفی که داریم ممکنه متفاوت باشه.

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

اما چرا به مدیریت خطا(error handling) نیاز داریم؟ کد پایین رو ببینید:

$node main.js
fs.js:641
  return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode);
                 ^

Error: ENOENT: no such file or directory, open '/Users/Kedar/node.txt'
    at Error (native)
    at Object.fs.openSync (fs.js:641:18)
    at Object.fs.readFileSync (fs.js:509:33)
    at Object.<anonymous> (/home/cg/root/7717036/main.js:3:17)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)

واسه سلامتی روانمون بهتره خطاها رو بشناسیم و مدیریتشون کنیم 🙂

انواع خطاها:Operational و Programmer

در اینجا ما خطاها رو به دو نوع تقسیم می‌کنیم که دلیل این تقسیم بندی رو بعدا میگم:

Operational Errors(خطاهای عملیاتی)

تمام ارور‌هایی که بخاطر runtime اتفاق می‌افته رو Operational Errors میگیم(runtime جاییه که کد‌های جاوااسکریپت توی اون اجرا میشن. به مجموعه‌ای از ابزار‌هایی که وجود داره تا این کدها اجرا بشن میگیم runtime. مثلا node.js خودش یه runtime هست).

این خطاها به این معنی نیستن که خود برنامه مشکلی داره. بلکه ممکنه اتفاق بیوفتن و به همین دلیل باید مدیریت بشن. برای مثال اینها نمونه‌هایی از Operational Errors هستن:

  • وصل نشدن به سرور یا دیتابیس
  • Request timeout
  • وارد شدن یه مقدار نامعتبر توسط کاربر
  • پیدا نشدن یک فایل
  • جواب 500 از طرف سرور(server error)

Programmer Errors(خطاهای برنامه نویس)

Programmer Errors همون چیزیه که بهش میگیم باگ. به مشکلاتی که خود کد داره میگیم Programmer Error. مثلا خوندن یه مقدار از undefined. اگه بخوایم مثال‌هایی از این ارور بزنیم:

  • catch نکردن(نگرفتن) یه promise که ریجکت شده.
  • فرستادن ورودی نامعتبر به function
  • سینتکس ارور‌ها
  • صدا زدن ایندکس یک آرایه در صورتی که اون ایندکس رو نداشته باشه.

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

بنظرتون ما دوست داریم برناممون رو راه اندازی مجدد کنیم در صورتی یه ارور not found بوجود بیاد؟ البته که نه! ممکنه یه کاربر پیدا نشه یا مشکلاتی براش بوجود بیاد ولی برنامه برای بقیه به خوبی کار می‌کنه. این یه مثال از Operational Errors بود.

اما اگه یه rejected promise رو نتونیم catch کنیم چی؟ اونوقت منطقیه که برنامه متوقف بشه تا باگ رو درست کنیم و دوباره اجراش کنیم. حالا که تا اینجا اومدیم بیاید ببینیم error object توی جاوااسکریپت چیه!

ارور آبجکت(Error Object)

Error Object یه آبجکت درونی یا built-in برای node.js هست که مجموعه‌ای از اطلاعات درمورد ارور بهمون میده:

const error = new Error("An error message")
console.log(error.stack)
// Error: An error message
//     at Object.<anonymous> (e:\Programming\personal apps\test-article\error-handling-in-js(1)\article.js:1:15)
//     at Module._compile (node:internal/modules/cjs/loader:1562:14)
//     at Object..js (node:internal/modules/cjs/loader:1699:10)
//     at Module.load (node:internal/modules/cjs/loader:1313:32)
//     at Function._load (node:internal/modules/cjs/loader:1123:12)
//     at TracingChannel.traceSync (node:diagnostics_channel:322:14)
//     at wrapModuleLoad (node:internal/modules/cjs/loader:217:24)
//     at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)
//     at node:internal/main/run_main_module:36:49

حالا برسیم به بخش اصلی قضیه:

اگه به node.js علاقه دارید شروع سریع با Selenium در Node.js برای اسکرپینگ وب بنظرم مطلب جالبه!

چگونه ارورها را در node.js مدیریت کنیم:

راه‌هایی برای Error Handling هست که توضیح میدم. برای مواردی که بنظرم ممکنه سنگین باشن هم صرفا گذرا میگم تا برنامه‌نویس های تازه کارتر گیج نشن. فعلا بیاید به روش‌هایی که با اون می‌تونیم ارور‌ها رو نشون بدیم نگاه بندازیم:

استفاده از callback function

استفاده از callback function یکی از راه‌هاست. اینطوری که اونها رو معمولا به عنوان آخرین آرگومان تابع اصلی قرار میدن تا قبل از استفاده از نتیجه تابع اصلی ارورها رو بررسی کنه! و در صورت وجود اونا رو بگیره. بزارید یه مثال بزنیم:

import fs from 'fs';

fs.readFile('/home/Kedar/node.txt', (err, result) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(result);
});

آرگومان دوم readFile یک callback function میگیره که اینطوری می‌تونیم در صورت وجود ارور اونو بگیریم. دقت کنید که الزاما همه متد‌ها همچین ویژگی ندارن :/

این راه رو گفتیم اما یه مشکل جدی داره و اون اینکه ممکنه توی “callback hell” یا همون جهنم callback تو در تو ایجاد بشه و اگه بخواهیم توی تابع‌های تو در تو دنبال ارور بگردیم پیدا کردن ارور می‌تونه خیلی سخت بشه!

پس از چه راهی استفاده کنیم؟ راه بهتر استفاده از async/await و statement های try-catch یا استفاده از ()catch. برای promise هاست. که ما این آخری رو نمیگیم می‌تونید خودتون بخونید(ما به تابع سازنده Promise دوتا تابع به عنوان آرگومان می‌فرستیم. یکی resolve و یکی reject. اولی در صورتی که promise حل بشه و دومی درصورتی که که حل نشه و نیاز باشه اونو کنترل کنیم).

استفاده از async/await و بلوک try-catch:

از اونجا که استاد سخن سعدی می‌فرمایند:

“بزرگی سراسر به گفتار نیست
دو صد گفته چون نیم کردار نیست”

اول مثال پایین رو ببینید:

try {
  const result = await fetch("https://codeeeeeexplore.ir");
  console.log(result);
} catch (err) {
  console.error(err);
} finally {
  console.log('good mood');
}

کدهایی که ممکنه error داشته باشن رو داخل بلوک try قرار میدیم و اجرا میشن. در صورتی که اروری بوجود بیاد محتوای بلوک catch اجرا میشه و اون رو میگیره و می‌تونیم توی اون error handling رو انجام بدیم. در آخر می‌تونیم از finally استفاده کنیم یا نکنیم. این محتوای این بلوک چه در صورت وجود ارور چه نبودن ارور اجرا میشه.

استفاده از Event emitters:

برای گزارش خطا توی سناریو‌های پیچیده(مثل عملیات‌های async طولانی که می‌تونن باعث ایجاد خطا‌های متعدد بشن) می‌تونیم بطور پیوسته خطا‌ها رو منتشر کنیم و به اونها گوش بدیم. این روش هم هست اما بخاطر اینکه یکم پیچیده‌تر هست اینجا کاملا توضیح نمیدیم. شاید توی مقاله‌های دیگه این مسئله رو جدا توضیح دادیم.

اگه به این مقاله علاقه داشتین، مقاله چرا نباید از setTimeout در جاوااسکریپت استفاده کنیم؟ مرهم این موضوع هم بنظرم جالبه!

در ادامه چندتا روش که بنظرم درکش خیلی مشکل نیست رو میگم:

error handling in js picture

انجام دادن دوباره عملیات

بعضی وقتا یه سری خطاها می‌تونن بخاطر سیستم خارجی بوجود بیان. مثلا وقتی می خوایم یه api استفاده کنیم سرویس در دسترس نباشه یا اتصال مشکل داشته باشه و … اینجور مواقع می‌تونیم اون درخواست رو بعد از یه مدت زمان خاصی دوباره تکرار کنیم.

گزارش دادن به کاربر

خیلی از وقت‌ها نیاز هست که به کاربر گزارش بدیم چه مشکلی بوجود اومده. مثلا برای زمانی مشکلی از سمت کاربر هست که باید اونو حل کنه یا صرفا برای اطلاع دادن به کاربر.

استفاده از error های سفارشی شده برای کنترل کردن Operational Errors

بیاید یه سری ارور سفارشی(custom error) بنویسیم که هرجایی که خواستیم از اونها استفاده کنیم(اگه پروژه بزرگی داشته باشیم این روش می‌تونه کمکمون کنه). برای مثال اول میام یه baseError می‌نویسم که پایه‌ای برای بقیه ارور‌های سفارشی شده باشه باشه:

// baseError.js

export class BaseError extends Error {
  constructor(name, statusCode, isOperational, description) {
    super(description);

    Object.setPrototypeOf(this, new.target.prototype);
    this.name = name;
    this.statusCode = statusCode;
    this.isOperational = isOperational;
    Error.captureStackTrace(this);
  }
}

حالا بیاید یه فایل httpStatusCodes.js بسازیم:

// httpStatusCodes.js

export const httpStatusCodes = {
  OK: 200,
  BAD_REQUEST: 400,
  NOT_FOUND: 404,
  INTERNAL_SERVER: 500,
};

حالا می‌تونیم یه فایل اختصاصی به اسم api404Error.js بسازیم و یک خطای سفارشی شده رو از روی baseError مخصوص ارور 404 بسازیم:

// api404Error.js

import httpStatusCodes from './httpStatusCodes';
import BaseError from './baseError';

export class Api404Error extends BaseError {
 constructor (
 name,
 statusCode = httpStatusCodes.NOT_FOUND,
 description = 'Not found.',
 isOperational = true
 ) {
 super(name, statusCode, isOperational, description)
 }
}

خب حالا چطور ازش استفاده کنیم؟ هرجایی از کد که نیاز به error handling ارور 404 داشت استفاده می‌کنیم. برای مثال:

const Api404Error = require('./api404Error')

...
const user = await User.getUserById(req.params.id)
if (user === null) {
 throw new Api404Error(`User with id: ${req.params.id} not found.`)
}
...

مثل همین قضیه رو می‌تونیم برای ارور‌های دیگه هم انجام بدیم مثل 500، 400 و …

راه اندازی مجدد برنامه برای رسیدگی به خطاهای برنامه نویسی

از اونجایی که این خطاها می‌تونن باعث نشت حافظه(memory leaks) و استفاده زیاد از CPU بشن بهترین کار اینه که با استفاده از حالت کلاستر Node.js یا ابزاری مثل PM2، برنامه رو از کار بندازیم و به آرومی اونو مجددا راه اندازی کنیم. امیدوارم در آینده درمورد این موضوع بتونم بنویسم.

استفاده از یه مکان مشخص برای کنترل گزارش‌ها و هشدار دادن خطاها

فقط در همین حد بگم که برای پروژه‌های بزرگ‌تر ممکنه ما نیاز داشته باشیم که ارور‌ها و خطاها رو در یک قالب مشخصی گزارش کنیم و حتی اونها(و بقیه log ها) رو در یه جای مشخص ذخیره کنیم. توضیح دادن درمورد node.js logging هم یه مقاله جدا می‌خواد و از ابزار‌های winston و morgan می‌تونیم کمک بگیریم.

سعی کردم مقاله رو طوری بنویسم که برای همه راحت باشه. درصورتی که می‌خواین با موارد بیشتری در این زمینه آشنا بشین Node.js Error Handling Made Easy رو معرفی می‌کنم. امیدوارم قسمت‌های بعدی درمورد error handling رو هم به زودی براتون در دسترس قرار بدم تا به موضوعات دیگه‌ای بپردازیم!

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

پیشنهاد های کد اکسپلور