Menu

معالجة الأخطاء في Express: middleware الأخطاء وasync

معالج الأخطاء في Express هو middleware بأربعة وسائط، (err, req, res, next)، يُسجَّل بعد المسارات. يرسل إليه Express 5 الأخطاء المرمية، واستدعاءات next(err)، والوعود المرفوضة من المعالجات غير المتزامنة.

تحتوي هذه الصفحة على محررات قابلة للتشغيل. حرّر، شغّل، وشاهد النتيجة فوراً.

يتعامل Express مع الأخطاء عبر middleware خاص يأخذ أربعة وسائط، (err, req, res, next)، ويُسجَّل بعد كل المسارات. يرسل إليه Express كل خطأ يُرمى داخل مسار، وكل خطأ تمرره إلى next(err)، وفي Express 5 كل وعد مرفوض من معالج async. يقرر المعالج رمز الحالة ويرسل استجابة خطأ واحدة، فتستطيع بقية شيفرتك أن ترمي الأخطاء ببساطة.

يجيب GET /users/99 بـ 404 مع {"error":"No user with id 99"}، وتعرض وحدة التحكم السطر القادم من معالج الأخطاء. أرسل GET /users/1 فيجيب المسار بشكل طبيعي؛ لا يعمل معالج الأخطاء إلا حين يحدث خطأ ما.

الأخطاء التي يلتقطها Express نيابة عنك

هناك ثلاث طرق للوصول إلى معالج الأخطاء من مسار، وفي Express 5 تعمل الثلاث بالطريقة نفسها:

يجيب GET /async بـ 500 مع {"error":"Rejected in an async route"}؛ وينتهي GET /sync وGET /next في المعالج نفسه برسالتيهما. الآن احذف next من معاملات معالج الأخطاء وأرسل GET /sync مرة أخرى: بثلاثة معاملات يعامله Express كـ middleware عادي، فيتخطاه، ويجيب بصفحة الخطأ HTML الافتراضية.

الأخطاء غير المتزامنة: Express 5 مقابل Express 4

يعمل المسار /async لأن Express 5 يلتقط الوعد المرفوض. أما Express 4 فلم يكن يفعل: كان الخطأ يصبح رفضًا غير معالج ويبقى الطلب معلّقًا.

Express 4Express 5
throw في معالج متزامنيُلتقطيُلتقط
next(err)يُلتقطيُلتقط
وعد مرفوض من معالج asyncلا يُلتقط، ويبقى الطلب معلّقًايُلتقط
خطأ في setTimeout أو في callback لحدثلا يُلتقط، وتنهار العمليةلا يُلتقط، وتنهار العملية

في Express 4، غلّف كل معالج غير متزامن أو ثبّت الحزمة express-async-errors:

// Express 4 only
const asyncHandler = (fn) => (req, res, next) =>
    Promise.resolve(fn(req, res, next)).catch(next);

app.get('/users/:id', asyncHandler(async (req, res) => {
    const user = await db.findUser(req.params.id);
    res.json(user);
}));

الصف الأخير متطابق في النسختين: لا يستطيع Express رؤية خطأ رُمي في callback لم يستدعه هو. حوّل واجهات callback إلى وعود (لمعظم Node نسخة بالوعود، مثل fs/promises) وانتظرها بـ await.

معالج 404 وخطأ JSON بشكل موحّد

تصبح واجهة API أسهل في الاستخدام حين يكون لكل خطأ الشكل نفسه. أعطِ الأخطاء status، وحوّل المسارات غير المطابقة إلى خطأ 404، ودع معالجًا واحدًا ينسّقها كلها:

لا يطابق GET /nope أي مسار، فيحوّله معالج 404 إلى خطأ وتكون الإجابة {"error":"Cannot GET /nope","status":404}. أرسل GET /users/99 فيأخذ الخطأ الذي رماه المسار الشكل نفسه. أرسل POST /users مع الجسم {"name": }: يفشل express.json() في تحليله ويستدعي next() مع خطأ قيمة status فيه 400، فتكون الإجابة {"error":"Unexpected token '}', \"{\"name\": }\" is not valid JSON","status":400}.

لا تكشف تتبّع المكدس

رسائل الأخطاء الصادرة من HttpError الخاص بك مكتوبة للعميل. أما الرسائل وتتبّعات المكدس الصادرة من أي شيء آخر (برنامج تشغيل قاعدة بيانات، أو خطأ إملائي يرمي TypeError) فقد تكشف مسارات الملفات والاستعلامات ونسخ المكتبات. يرسل المعالج السابق Internal server error مع كل خطأ 5xx ويسجّل الخطأ الحقيقي على الخادم بدلًا من ذلك. ويغطي if (res.headersSent) return next(err); خطأ يحدث بعد أن يكون جزء من الاستجابة قد أُرسل: الخطوة الآمنة الوحيدة حينها هي أن تترك Express يغلق الاتصال.

يتبع المعالج الافتراضي في Express قيمة NODE_ENV: يضمّن تتبّع المكدس في صفحة HTML الخاصة به ما لم تكن NODE_ENV هي production. اضبطها على خادم الإنتاج (NODE_ENV=production node server.js)، لكن لا تعتمد عليها: المعالج المخصص الذي لا يرسل err.stack أبدًا أكثر أمانًا.

أخطاء شائعة

تسجيل معالج الأخطاء قبل المسارات. يمرر Express الأخطاء إلى الأمام فقط، فالمعالج الواقع فوق مسار لا يرى أخطاء ذلك المسار أبدًا. ضعه في النهاية، بعد معالج 404.

التقاط خطأ وعدم فعل شيء به. الـ try/catch الذي يكتفي بالتسجيل يترك الطلب معلّقًا. في catch استدعِ next(err)، أو ارمِ الخطأ من جديد، أو أرسل استجابة.

إرسال استجابتين. الرمي بعد res.json() يجعل معالج الأخطاء يحاول الإرسال مرة أخرى. افحص res.headersSent، كما في المثال.

لمعرفة كيف يعمل الـ middleware العادي وnext()، راجع Middleware في Express.

الأسئلة الشائعة

هل يمكن أن يكون لدي أكثر من معالج أخطاء في Express؟

نعم. تعمل بالترتيب، وكل واحد منها إما يرسل استجابة أو يستدعي next(err) ليمرر الخطأ. ومن التقسيمات الشائعة معالج يسجّل الخطأ وآخر يرسل الاستجابة.

ماذا يحدث إذا رمى معالج الأخطاء خطأ؟

يمرر Express الخطأ الجديد إلى معالج الأخطاء التالي، وإن لم يوجد، إلى معالجه المدمج الذي يرسل صفحة 500. أبقِ معالجات الأخطاء بسيطة كي لا تفشل هي نفسها.

أي رمز حالة يجب أن تستخدمه استجابة الخطأ؟

استخدم 4xx حين يرسل العميل شيئًا خاطئًا (400 مدخلات غير صالحة، و401 غير مسجّل الدخول، و403 غير مسموح، و404 غير موجود، و409 تعارض) و500 حين تفشل شيفرتك. لا تُجب أبدًا بـ 200 وفي الجسم خطأ.

كيف أعيد أخطاء التحقق لعدة حقول؟

أجب بـ 400 مع قائمة، مثل { "errors": [{ "field": "email", "message": "is required" }] }. مع zod، حوّل result.error.issues إلى هذا الشكل داخل معالج الأخطاء لتبلّغ كل المسارات عن المدخلات الخاطئة بالطريقة نفسها.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن