T1 · Intermediate · 8 دقيقةثابت · آخر مراجعة: 2026-08-19مسودة أولى — بنراجعها

الـ Timeouts والـ Idempotency — طلبات ما تتكرّرش بالغلط

في سطر واحد · بتحطّ سقف زمني للطلب (timeout) عشان متستنّاش للأبد، وبتستخدم مفتاح idempotency عشان لو أعدت الطلب مايتنفّذش مرّتين.

القاموس:IdempotencyInferenceTrace
ليه ده مهم؟

الموديلات بطيئة أحيانًا، ومن غير timeout هيعلّق منتجك؛ ومن غير idempotency الـ retry ممكن يعمل نفس الحاجة مرّتين — يبعت إيميلين أو يخصم مرّتين.

الـ timeout زي مؤقّت المكوة: لو فضل شغّال أكتر من اللازم يقفل لوحده. والـ idempotency زي رقم الأوردر: تبعت نفس الرقم تاني، النظام يعرف إنه نفس الطلب ومايكرّرهوش.

خلينا نفهمها بمثال

الـ idempotency key زي إيصال الطلب في المطعم: لو الويتر اختفى وإنت مش عارف سمع الأوردر ولا لأ، وطلبت تاني بنفس رقم الإيصال، المطبخ يعرف إنه نفس الطلب فمايطبخوش مرّتين.

طلب علّق ٣٠ ثانية فالـ timeout يقطعه — بس خلّي بالك، القطع ده مبيقولكش إذا كان السيرفر خلّص الطلب ولا لأ؛ فتعيد بنفس الـ idempotency key، ولو الأول كان اتنفّذ فعلًا، السيرفر يرجّع نفس النتيجة بدل ما يعملها تاني.

طيب بتحصل إزاي؟

بتحطّ timeout على الـ call (بـ AbortController مثلًا) فالطلب المعلّق يتقطع بدل ما يعلّق منتجك. بس هنا اللقطة المهمة: الـ timeout بيقطع من ناحيتك إنت بس — مبيقولّكش السيرفر نفّذ الشغل ولا لأ؛ ممكن يكون خلّصه بعد ما إنت استسلمت. عشان كده الطلبات اللي ليها side effect بتاخد idempotency key: معرّف فريد للعملية الواحدة بتبعته في header. لما تعيد بنفس المفتاح، السيرفر يتعرّف عليه — لو العملية كانت اتنفّذت، يرجّعلك نفس النتيجة المخزّنة بدل ما ينفّذها تاني.

import { randomUUID } from "crypto";

const idempotencyKey = randomUUID(); // one key per OPERATION, not per attempt

async function safeCall() {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), 30_000);
  try {
    return await fetch(url, {
      method: "POST",
      signal: controller.signal,
      headers: {
        Authorization: `Bearer ${process.env.API_KEY}`,
        "Idempotency-Key": idempotencyKey, // same key on every retry
      },
      body,
    });
  } finally {
    clearTimeout(timer);
  }
}
خلينا نبنيها

🧪 جرّب بنفسك: ابني mock endpoint بيخزّن النتايج بالـ Idempotency-Key، وكلاينت بيعمل timeout للمحاولة الأولى (خلّي السيرفر يستنّى أطول من الـ timeout عمدًا) وبعدين يعيد بنفس المفتاح. اتأكد إن العملية اتنفّذت مرة واحدة بس — وبعدين جرّب بمفتاح جديد كل مرة وشوف الكارثة بنفسك.

أشهر الأخطاء

(١) تفترض إن timeout معناه «الطلب ماتنفّذش» وتعيد عملية ليها side effect من غير مفتاح — فيتبعت إيميلين أو يتخصم مرّتين. (٢) تولّد idempotency key جديد مع كل محاولة retry — كده ضيّعت الفكرة كلها؛ المفتاح للعملية الواحدة مش للمحاولة الواحدة. (٣) تحطّ timeout قصير أوي على توليد الموديلات — الردود الطويلة من غير streaming بتاخد ٣٠ ثانية+ عادي، فتلاقي نفسك بتقطع طلبات سليمة.

إمتى تستخدمها؟

timeout على كل network call من غير استثناء؛ وidempotency key على أي طلب الـ retry بتاعه ممكن يكرّر side effect: خصم فلوس، إرسال إيميل، كتابة في داتابيز.

وإمتى لأ؟

القراءات البحتة (GET وأمثاله) مش محتاجة idempotency keys — تكرارها مبيضرش، فمتضيفش بنية زيادة على الفاضي. الـ timeout بيفضل مطلوب برضه هناك — بس المفتاح لأ.

خلينا نبنيهاالقراءة خلصت — دلوقتي ابنيها بإيدك.اعمل retry آمن بمفتاح idempotency