Mohammad Jawad Barati
Mohammad Jawad Barati
خواندن ۷ دقیقه·۴ سال پیش

ارور هندلینگ تو یه وب اپ expressjs ای

خب چه ارور هایی ممکنه رخ بده تو یه وب سرور expressjs ای؟

  • ارور های سنکرون توی میدلویر ها مثل اینا، اگه هندل نشده باشن برنامت یه صفحه نشون میده که توش stack ارور نوشته شده
app.use('/', (req, res, next) => { throw new Error('asdasd') }); app.use('/', (req, res, next) => { fs.readFileSync('file-does-not-exist') });
  • ارور های آسنکرون توی میدلویر ها که باعث بسته شدن پروسه برنامه میشن، اگه هندل نشده باشن.
app.use('/', (req, res, next) => { setTimeout(() => { throw new Error('async error') }, 1000); });
  • ارور های سنکرون بیرون از میدلویر ها که اگه هندل نشده باشن پروسه برنامه رو نمی‌بندن ولی خب ادمینم چیزی نمیفهمه.
cron.schedule('*/1 * * * *', async () => { throw new Error('out of middleware') });
  • ارور های آسنکرون بیرون از میدلویر ها مشابه این باعث بسته شدن پروسه برنامتون میشن.
cron.schedule('*/1 * * * *', async () => { setTimeout(() => { throw new Error('out of middleware') }, 1000); });

ارور هندلینگ ارور های سنکرون توی میدلویر ها

حالا که از نزدیک خطرات ارور ها رو دیدی بیا یه فکری به حالشون بکنیم. برای ارور های سنکرون توی میدلویر ها میتونی ارور هندلر اکسپرسی تعریف بکنی و دیگه خیالت از بابت این ارور ها که تو میدلویرات رخ میده راحت باشه مثل این:

// error handler middleware app.use((error, req, res, next) => { console.error(error) res.status(error.statusCode || 500).json({ &quoterror&quot: error.message || &quotInternal error occured&quot }); });

خب این اولین قدم و ساده ترین قدم هست. این تیکه کد رو به عنوان آخرین میدلویر برای اپ اکسپرس تعریف کنید. توی این میدلویر میتونی یه صفحه html ای رندر بکنی یا حتی به ادمین خبر بدی (مثلا ایمیل بهش بزنی) که یه اروری رخ داده تو route فلان.


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

تو این داستان فقط کافیه بیای ارور ها رو catch بکنی (مثلا try catch بزاری یا then catch استفاده بکنی) و بعدش اون ارور ها رو به میدلویر هندل کردن ارور ها بفرستی. مثل این:

app.get('/', (req, res, next) => { setTimeout(() => { try { throw new Error('this will call error handler middleware') } catch (err) { next(err) }; }, 100) }); app.get('/', function (req, res, next) { fs.readFile('/file-does-not-exist', function (err, data) { if (err) next(err) // Pass errors to Express error handler middleware else res.send(data) }) });

البته میتونی بیای کال‌بک رو به promise تبدیل بکنی با روشی که اینجا توضیح داده شده یا از کتاخونه های native خود نود.جی‌اس برای اینکار استفاده بکنید، برای مطالعه بیشتر تو این مورد به این و این مراجعه بکنید.

تو اکسپرس ورژن ۵ وقتی یه میدلویر async باشه و اروری توش رخ بده، next به صورت خودکار با مقدار reject شده فراخونی میشه. اگرم مقداری reject نشده باشه مقدار پیشفرض آبجکت ارور به next به عنوان آرگومان داده میشه.

app.get('/user/:id', async (req, res, next) => { let user = await getUserById(req.params.id); res.send(user); });

پس تا اینجا قضیه با یه ارور هندلر تعریف کردن حل شده. حالا اگه یه فانکشنی که خودت تعریف کردی رو تو یه میدلویر استفاده بکنی و اون فانکشن یه ارور تولید بکنه چی؟ قضیه خیلی ساده هست. برای درک بهتر موضوع این پست رو بخون. اگرم حوصله نداری بری سراغ اون پست در این حد بدون که تو اون فانکشن ارور throw میشه. بعدش جاوااسکریپت با نگاه انداختن به call stack خودش دنبال اولین catch میگرده.

اگه این catch رو تو میدلویرت تعریف کرده باشی جاوااسکریپت میاد اون catch رو اجرا میکنه. حالا تو اون catch تو میتونی `next(error)` بکنی مثل:

// example 1 app.get('/', async (req, res, next) => { try { throwError() } catch (error) { next(error) }; }); function throwError() { throw new Error('sync or async, it does not matter') }; // example 2 app.get('/', async (req, res, next) => { try { let data = await readFileFunc(); res.end(); } catch (error) { next(error); }; }); async function readFileFunc() { return await fs.promises.readFile('this-is-not') };

البته یادت باشه که اگه تو یه callback بیای ارور throw بکنی، اون ارور توسط هیچ try catch هندل نمیشه. بعضی موقع ها هم میتونی کارتو یه خورده ساده بکنی وقتی فقط ممکنه ارور رخ بده یا اصلا دیتایی نداره:

app.get('/', [ (req, res, next) => { fs.writeFile('/inaccessible-path', 'data', next) }, (req, res) => { res.send('OK') } ]);

نکات دانه

  • اگه خودت یه میدلویر ارور هندلر ننوشته باشی، اکسپرس از ارور هندلر پیشفرض خودش که ارور رو به همراه stack ارور تو صفحه وب نمایش میده استفاده میکنه.
  • اگه داری استریمینگ میکنی یه چیزی رو و ارور هندلر کاستوم خودت رو نوشتی. یعنی یه response ای داری میفرستی ولی بعد از اون یهو یه اروری رخ بده تو نمیتونی header ها رو rewrite بکنی و ارور هندلر رو فراخونی بکنی، مگه اینکه تو ارور هندلرت نوشته باشی:
function errorHandler (err, req, res, next) { if (res.headersSent) { return next(err) } res.status(err.status || 500).json({ 'error': err }); };
  • میتونی بیشتر از یه ارور هندلر تعریف بکنی. مثلا ۲ تا بنویسی. یکی برای لاگ انداختن و دومی برای ارسال پاسخ به کلاینت. مثل:
app.use((error, req, res, next) => { console.error(error); next(error); // you should call next(error) or send a response }); app.use((error, req, res, next) => { res.status(500).json({ error: 'Server side error occured' }); })

ارور هندلینگ ارور های سنکرون بیرون از میدلویر ها

از اونجایی که این ارور ها پروسه برنامه رو نمیبندن (تا جایی که من فهمیدم) بهترین کار (که من فعلا بهش رسیدم) اینه که بیای از process.on استفاده بکنی و ارور هایی رو که هندل نکردی (بنا به هر دلیلی) مثل این زیره هندل بکنی.

process.on('uncaughtException', error => { logger.error('Uncaught error occured', { &quottimestamp&quot: new Date().toLocaleString(), &quotmessage&quot: error.message, &quotstack&quot: error.stack }); // send an email to admin or show admin a notification in panel });

ارور هندلینگ ارور های آسنکرون بیرون از میدلویر ها

از اونجایی که این ارور ها رو باید هندل کرد و یه جوری به ادمین خبر داد که همچین اتفاقی افتاده راهی که فعلا به ذهنم میرسه اینه که از این روش استفاده بکنن:

process.on('unhandledRejection', error => { logger.error('Unhandled promise rejection', { &quottimestamp&quot: new Date().toLocaleString(), &quotmessage&quot: error.message, &quotstack&quot: error.stack }); });

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

error handling
برنانه نویس، مدرس، محقق. عاشق انیمه هستم و دنبال چالش ها جدید.
شاید از این پست‌ها خوشتان بیاید