خب چه ارور هایی ممکنه رخ بده تو یه وب سرور expressjs ای؟
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({ "error": error.message || "Internal error occured" }); });
خب این اولین قدم و ساده ترین قدم هست. این تیکه کد رو به عنوان آخرین میدلویر برای اپ اکسپرس تعریف کنید. توی این میدلویر میتونی یه صفحه 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') } ]);
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', { "timestamp": new Date().toLocaleString(), "message": error.message, "stack": error.stack }); // send an email to admin or show admin a notification in panel });
از اونجایی که این ارور ها رو باید هندل کرد و یه جوری به ادمین خبر داد که همچین اتفاقی افتاده راهی که فعلا به ذهنم میرسه اینه که از این روش استفاده بکنن:
process.on('unhandledRejection', error => { logger.error('Unhandled promise rejection', { "timestamp": new Date().toLocaleString(), "message": error.message, "stack": error.stack }); });
بهتره برای مطالعه بیشتر رفرنس انگلیسی خود اکسپرس رو هم بخونید. البته اگه دوستی این مطلب رو خوند و نقصی توش دید یه کامنت زیر پست بزاره و نواقص رو بگه تا مرتفع بکنم.