خطأ setTimeout النمطي في TypeScript: window مقابل Node.js

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

"ليس setTimeout الصحيح": كيف يدفع TypeScript المطورين إلى الجنون

\n

هل سبق لك أن كتبت مؤقتًا بسيطًا بلغة TypeScript، وفجأة بدأ محرر الكود بوضع خطوط حمراء تحت كل شيء؟ مشهد مألوف: تقوم باستيراد setTimeout، وتكتب const timer = setTimeout(...)، ويظهر TypeScript خطأً أو يقترح نوعًا غير مفهوم. ما المشكلة؟ اتضح أن setTimeout له نسختان - واحدة للمتصفح (window) والأخرى للخادم (Node.js). و TypeScript، بصفته مثالياً، يجبرنا على الاختيار.

\n

هذا الموضوع هو واحد من أكثر المواضيع شيوعًا على Stack Overflow. آلاف المطورين يتعثرون بنفس المشكلة. لكننا لن نقوم فقط بنسخ الإجابة. سنفهم جذر المشكلة، وكيف تعمل كتابة الأنواع، ونتعلم كتابة كود لا ينكسر لا في المتصفح ولا في Node.js.

\n\n

لماذا توجد نسختان من setTimeout؟

\n

تاريخيًا، يعيش JavaScript في عالمين: العميل (المتصفح) والخادم (Node.js). في المتصفح، setTimeout هي طريقة للكائن العام window. في Node.js، هي دالة من الوحدة العامة timers. كلاهما يفعل نفس الشيء، لكن توقيعات أنواعهما مختلفة.

\n

إليك الاختلافات الرئيسية:

\n
    \n
  • القيمة المُرجعة: في المتصفح، window.setTimeout يُرجع number (معرف المؤقت). في Node.js، يُرجع كائنًا من نوع NodeJS.Timeout.
  • \n
  • السياق: في المتصفح، this داخل callback هو window (في الوضع غير الصارم). في Node.js، هو كائن المؤقت.
  • \n
  • كتابة الأنواع: يوفر TypeScript تصريحات مختلفة لبيئات مختلفة. إذا كنت تكتب كودًا عالميًا (تطبيق متشابه الشكل)، ينشأ تعارض.
  • \n
\n\n

كيف تبدو المشكلة في الكود

\n

تخيل أنك تطور مكتبة يجب أن تعمل على كل من العميل والخادم. تكتب الكود التالي:

\n
// utils.ts\nexport function delay(ms: number): Promise<void> {\n    return new Promise(resolve => {\n        const timer = setTimeout(resolve, ms);\n        // ما نوع timer؟ number أم NodeJS.Timeout؟\n    });\n}
\n

سيرى TypeScript أن setTimeout يمكن أن يكون من نوعين، وسيظهر خطأ: "Type 'Timeout' is not assignable to type 'number'." أو العكس، اعتمادًا على إعدادات tsconfig.json والأنواع المتصلة.

\n

لتوضيح الأمر أكثر، إليك كيف تبدو الأنواع عند النظر إليها من خلال ReturnType:

\n
// في المتصفح (lib.dom.d.ts)\ntype BrowserTimer = ReturnType<typeof window.setTimeout>; // number\n\n// في Node.js (@types/node)\ntype NodeTimer = ReturnType<typeof setTimeout>; // NodeJS.Timeout
\n\n

الحلول الصحيحة لسيناريوهات مختلفة

\n

دعنا نستعرض أربعة طرق. اختر الطريقة التي تناسب مشروعك.

\n\n

1. إذا كنت تكتب فقط للمتصفح

\n

أبسط حالة. فقط حدد النوع number واستخدم window.setTimeout بشكل صريح:

\n
// browser-timer.ts\nconst timerId: number = window.setTimeout(() => {\n    console.log('لقد مرت ثانية');\n}, 1000);\n\n// للإزالة\nwindow.clearTimeout(timerId);
\n

لماذا يعمل هذا؟ في بيئة المتصفح، window.setTimeout يُرجع دائمًا رقمًا. TypeScript يعرف ذلك إذا كان لديك lib: ["dom"] متصلاً.

\n\n

2. إذا كنت تكتب فقط لـ Node.js

\n

هنا النوع هو NodeJS.Timeout. استخدمه مباشرة:

\n
// node-timer.ts\nimport { setTimeout } from 'timers'; // استيراد صريح للوضوح\n\nconst timer: NodeJS.Timeout = setTimeout(() => {\n    console.log('تم تشغيل المؤقت');\n}, 2000);\n\nclearTimeout(timer); // clearTimeout يقبل NodeJS.Timeout
\n

هام: في Node.js، clearTimeout يقبل أيضًا No

المدونات

توصيات الكتب