مرحبا زملائي. ذات مرة في حبري
، تم تأليف
مقال تحت تأليف جون ريزيج حول هذا الموضوع. لقد مرت 10 سنوات ، ولا يزال الموضوع بحاجة إلى توضيح. لذلك ، نقدم للمهتمين قراءة المقالة بقلم Samer Buna ، والتي لا تقدم نظرة عامة نظرية للمؤقتات في JavaScript (في سياق Node.js) فحسب ، بل أيضًا مهام عليها.

قبل بضعة أسابيع غردت عن السؤال التالي من مقابلة واحدة:
"أين هو رمز المصدر لوظائف setTimeout و setInterval؟ أين تبحث عنه؟ لا يمكنك البحث عنه :) "
*** أجب عنه بنفسك ، ثم اقرأ على ***
حوالي نصف الردود على هذه التغريدة كانت خاطئة. لا ، القضية ليست ذات صلة بـ V8 (أو VMs أخرى) !!! وظائف مثل
setTimeout
و
setInterval
، تسمى بفخر JavaScript JavaScript Timers ، ليست جزءًا من أي مواصفات ECMAScript أو تطبيق محرك JavaScript. يتم تنفيذ وظائف المؤقت على مستوى المتصفح ، لذلك يختلف تنفيذها في المتصفحات المختلفة. يتم تنفيذ المؤقتات أيضًا في Node.js. runtime نفسه.
في المستعرضات ، تتعلق وظائف المؤقت الرئيسية بواجهة
Window
، والتي ترتبط أيضًا ببعض الوظائف والكائنات الأخرى. توفر هذه الواجهة وصولًا عالميًا إلى جميع عناصره في النطاق الرئيسي لجافا سكريبت. هذا هو السبب في أنه يمكن تنفيذ وظيفة
setTimeout
مباشرة في وحدة تحكم المتصفح.
في Node ، تعد المؤقتات جزءًا من الكائن العام ، والذي تم تصميمه مثل واجهة المتصفح الخاصة بـ
Window
. يظهر رمز المصدر للمؤقتات في العقدة
هنا .
قد يبدو لشخص ما أن هذا مجرد سؤال سيء من المقابلة - ما فائدة معرفة ذلك ؟! أنا ، كمطور جافا سكريبت ، أفكر بهذه الطريقة: من المفترض أنه يجب أن تعرف ذلك ، لأن العكس قد يشير إلى أنك لا تفهم تمامًا كيفية تفاعل V8 (والأجهزة الظاهرية الأخرى) مع المتصفحات والعقدة.
دعونا نلقي نظرة على بعض الأمثلة ونحل بعض المهام المؤقتة ، دعنا؟
يمكنك استخدام أمر العقدة لتشغيل الأمثلة في هذه المقالة. ظهرت معظم الأمثلة التي تمت مناقشتها هنا في دورة " البدء مع Node.js" حول Pluralsight.تنفيذ وظيفة مؤجلةالمؤقتات هي وظائف أعلى مرتبة يمكنك من خلالها تأخير أو تكرار تنفيذ وظائف أخرى (يتلقى المؤقت وظيفة مثل الوسيط الأول).
فيما يلي مثال على التنفيذ المؤجل:
في هذا المثال ، باستخدام
setTimeout
تتأخر رسالة الترحيب لمدة 4 ثوانٍ. الوسيطة الثانية ل
setTimeout
هي التأخير (بالمللي ثانية). أضرب 4 في 1000 لتحصل على 4 ثوان.
الوسيطة الأولى لـ
setTimeout
هي وظيفة سيتم تأجيل تنفيذها.
إذا قمت بتنفيذ ملف
example1.js
باستخدام أمر العقدة ، ستتوقف العقدة مؤقتًا لمدة 4 ثوانٍ ثم تعرض رسالة ترحيب (متبوعة بخروج).
يرجى ملاحظة: الوسيطة الأولى
setTimeout
هي مجرد
مرجع دالة . لا يجب أن تكون دالة
example1.js
- مثل
example1.js
. هذا هو نفس المثال دون استخدام الوظيفة المضمنة:
const func = () => { console.log('Hello after 4 seconds'); }; setTimeout(func, 4 * 1000);
تمرير الحججإذا كانت الدالة التي تُستخدم لها
setTimeout
لتأخير تقبل أي وسيطات ، فيمكنك استخدام الوسيطات المتبقية للدالة
setTimeout
نفسها (بعد 2 التي درسناها بالفعل) لنقل قيم الوسيطات إلى الدالة المؤجلة.
هنا مثال:
تأخذ وظيفة
rocks
المذكورة أعلاه ، التي تأخرت لمدة ثانيتين ، الوسيطة
who
، واستدعاء
setTimeout
يمررها إلى القيمة "Node.js" مثل الوسيطة
who
.
عند تنفيذ
example2.js
باستخدام الأمر
node
، سيتم عرض العبارة "Node.js rocks" بعد ثانيتين.
مهمة المؤقتات # 1لذلك ، استنادًا إلى المادة التي تم دراستها بالفعل حول
setTimeout
،
setTimeout
رسالتين التاليتين بعد التأخير المقابل.
- يتم عرض رسالة "مرحبًا بعد 4 ثوانٍ" بعد 4 ثوانٍ.
- يتم عرض رسالة "مرحبًا بعد 8 ثوانٍ" بعد 8 ثوانٍ.
حدودفي الحل الخاص بك ، يمكنك تحديد وظيفة واحدة فقط تحتوي على وظائف مضمنة. هذا يعني أن العديد من استدعاءات
setTimeout
يجب أن تستخدم نفس الوظيفة.
الحلإليك كيفية حل هذه المشكلة:
بالنسبة لي ، يتلقى
theOneFunc
وسيطة
delay
ويستخدم قيمة وسيطة
delay
هذه في الرسالة المعروضة على الشاشة. وبالتالي ، يمكن أن تعرض الوظيفة رسائل مختلفة اعتمادًا على قيمة التأخير التي سنبلغها بها.
ثم استخدمت
theOneFunc
في
theOneFunc
setTimeout
، مع تشغيل المكالمة الأولى بعد 4 ثوانٍ والثانية بعد 8 ثوانٍ. يتلقى كل من مكالمات
setTimeout
هذه أيضًا وسيطة ثالثة ، تمثل وسيطة
delay
لـ
theOneFunc
.
من خلال تنفيذ ملف
solution1.js
باستخدام أمر العقدة ، سنعرض متطلبات المهمة ، وستظهر الرسالة الأولى بعد 4 ثوانٍ ، والثانية بعد 8 ثوانٍ.
كرر الوظيفةولكن ماذا لو طلبت منك عرض رسالة كل 4 ثوانٍ لفترة غير محدودة؟
بالطبع ، يمكنك
setTimeout
في حلقة ، ولكن واجهة برمجة تطبيقات المؤقت تقدم أيضًا وظيفة
setInterval
، والتي يمكنك من خلالها برمجة التنفيذ "الأبدي" لأي عملية.
فيما يلي مثال على
setInterval
:
سيعرض هذا الرمز رسالة كل 3 ثوانٍ. إذا قمت بتنفيذ
example3.js
باستخدام الأمر
node
،
example3.js
Node هذا الأمر حتى تقوم
example3.js
إنهاء العملية (CTRL + C).
إلغاء الموقتاتنظرًا لأنه يتم تعيين إجراء عندما يتم استدعاء وظيفة المؤقت ، يمكن أيضًا التراجع عن هذا الإجراء قبل تنفيذه.
تقوم استدعاء
setTimeout
بإرجاع معرّف مؤقت ، ويمكنك استخدام معرّف المؤقت عند الاتصال بـ
clearTimeout
لإلغاء المؤقت. هنا مثال:
يجب تشغيل هذا المؤقت البسيط بعد 0 مللي ثانية (أي على الفور) ، ولكن هذا لن يحدث ، لأننا نلتقط قيمة
timerId
هذا المؤقت فورًا عن طريق استدعاء
clearTimeout
.
عند تنفيذ
example4.js
باستخدام أمر
node
، لن تطبع العقدة أي شيء - ستنتهي العملية ببساطة على الفور.
بالمناسبة ، يوفر Node.js أيضًا طريقة أخرى
setTimeout
بقيمة 0 مللي ثانية. هناك وظيفة أخرى في واجهة برمجة تطبيقات جهاز
setImmediate
الوقت Node.js تسمى
setImmediate
، وهي تقوم بشكل أساسي بنفس الشيء مثل
setTimeout
بقيمة 0 مللي ثانية ، ولكن في هذه الحالة يمكنك حذف التأخير:
setImmediate( () => console.log('I am equivalent to setTimeout with 0 ms'), );
وظيفة
setImmediate
غير
مدعومة في جميع المتصفحات . لا تستخدمه في كود العميل.
إلى جانب
clearTimeout
هناك وظيفة
clearInterval
تفعل الشيء نفسه ، ولكن مع مكالمات
setInerval
، وهناك أيضًا مكالمة
clearImmediate
.
تأخير الموقت - شيء غير مضمونهل لاحظت أنه في المثال السابق ، عند تنفيذ عملية باستخدام
setTimeout
بعد 0 مللي ثانية ، لا تحدث هذه العملية على الفور (بعد
setTimeout
) ، ولكن فقط بعد تنفيذ كل كود البرنامج النصي بالكامل (بما في ذلك استدعاء
clearTimeout
)؟
دعني أوضح هذه النقطة بمثال. فيما يلي مكالمة
setTimeout
بسيطة يجب أن تعمل في نصف ثانية - ولكن هذا لا يحدث:
مباشرة بعد تحديد المؤقت في هذا المثال ، نقوم بحظر بيئة وقت التشغيل بشكل متزامن مع حلقة كبيرة. قيمة
1e10
هي 1 مع 10 أصفار ، لذا تستمر الدورة 10 مليار دورة معالج (من حيث المبدأ ، هذا يحاكي المعالج الزائد). لا تستطيع العقدة فعل أي شيء حتى تكتمل هذه الحلقة.
بالطبع ، هذا أمر سيئ جدًا في الممارسة العملية ، لكن هذا المثال يساعد على فهم أن تأخير
setTimeout
غير مضمون ، بل
الحد الأدنى للقيمة . تعني القيمة 500 مللي ثانية أن التأخير سيستمر 500 مللي ثانية على الأقل. في الواقع ، سيستغرق البرنامج النصي وقتًا أطول لعرض خط الترحيب على الشاشة. أولاً ، سيتعين عليه الانتظار حتى تكتمل دورة الحجب.
مشكلة المؤقتات # 2اكتب برنامجًا نصيًا يعرض رسالة "Hello World" مرة واحدة في الثانية ، ولكن 5 مرات فقط. بعد 5 تكرارات ، يجب أن يعرض البرنامج النصي رسالة "تم" ، وبعد ذلك ستكتمل عملية العقدة.
القيد : عند حل هذه المشكلة ، لا يمكنك استدعاء
setTimeout
.
تلميح : بحاجة إلى عداد.
الحلإليك كيفية حل هذه المشكلة:
let counter = 0; const intervalId = setInterval(() => { console.log('Hello World'); counter += 1; if (counter === 5) { console.log('Done'); clearInterval(intervalId); } }, 1000);
قمت بتعيين 0 كقيمة أولية
counter
، ثم سميت
setInterval
، والتي تأخذ معرفها.
ستعرض الوظيفة المؤجلة رسالة ، وفي كل مرة تزيد العداد بمقدار رسالة. داخل الدالة المؤجلة ، لدينا بيان if ، والذي سيتحقق مما إذا كانت 5 تكرارات قد مرت بالفعل. بعد 5 تكرارات ، يعرض البرنامج "تم" ويزيل قيمة الفاصل الزمني باستخدام ثابت
intervalId
التقاطه. الفاصل الزمني هو 1000 مللي ثانية.
من الذي يدعو بالضبط الوظائف المؤجلة؟عند استخدام JavaScript
this
داخل دالة عادية ، مثل هذه على سبيل المثال:
function whoCalledMe() { console.log('Caller is', this); }
ستتطابق القيمة في
this
مع
المتصل . إذا قمت بتعريف الوظيفة المذكورة أعلاه داخل عقدة REPL ، فسيطلق عليها الكائن العام. إذا قمت بتحديد وظيفة في وحدة تحكم المستعرض ،
window
كائن
window
.
دعنا نحدد دالة كخاصية كائن لجعلها أكثر وضوحا قليلا:
const obj = { id: '42', whoCalledMe() { console.log('Caller is', this); } };
الآن ، عندما نستخدم الرابط الخاص به مباشرةً عند العمل مع وظيفة
obj.whoCallMe
،
obj.whoCallMe
كائن
obj
(المحدد
obj.whoCallMe
:

السؤال الآن هو: من سيكون المتصل إذا مررت الرابط إلى
obj.whoCallMe
setTimetout
؟
من هو المتصل في هذه الحالة؟ستختلف الإجابة اعتمادًا على مكان تنفيذ وظيفة المؤقت. في هذه الحالة ، فإن الاعتماد على من هو المتصل غير مقبول ببساطة. ستفقد السيطرة على المتصل ، لأنه سيعتمد على تنفيذ المؤقت الذي في هذه الحالة يستدعي وظيفتك. إذا قمت باختبار هذا الرمز في Node REPL ، فسيكون كائن
Timeout
هو المتصل:

يرجى ملاحظة ما يلي: هذا مهم فقط عند استخدام JavaScript
this
داخل الوظائف العادية. عند استخدام وظائف الأسهم ، يجب ألا يزعجك المتصل على الإطلاق.
مشكلة المؤقتات # 3اكتب نصًا سينتج باستمرار رسالة "Hello World" بتأخيرات متفاوتة. ابدأ بتأخير ثانية واحدة ، ثم زدها بمقدار ثانية واحدة عند كل تكرار. في التكرار الثاني ، سيكون التأخير ثانيتين. على الثالث - ثلاثة ، وهلم جرا.
قم بتضمين تأخير في الرسالة المعروضة. يجب أن تحصل على شيء مثل هذا:
Hello World. 1
Hello World. 2
Hello World. 3
...
القيود : لا يمكن
تحديد المتغيرات إلا باستخدام const. استخدام let أو var ليس كذلك.
الحلنظرًا لأن مدة التأخير في هذه المهمة متغيرة ، فلا يمكنك استخدام
setInterval
هنا ، ولكن يمكنك تكوين التنفيذ الفاصل يدويًا باستخدام
setTimeout
داخل مكالمة عودية. ستقوم الوظيفة الأولى التي تم تنفيذها باستخدام
setTimeout
بإنشاء المؤقت التالي ، وهكذا.
بالإضافة إلى ذلك ، نظرًا لأنه لا يمكنك استخدام
let
/
var
، فلا يمكن أن يكون لدينا عداد لزيادة التأخير لكل مكالمة عودية ؛ بدلاً من ذلك ، يمكنك استخدام وسيطات دالة عودية لإجراء زيادة أثناء استدعاء عودي.
إليك كيفية حل هذه المشكلة:
const greeting = delay => setTimeout(() => { console.log('Hello World. ' + delay); greeting(delay + 1); }, delay * 1000); greeting(1);
مهمة المؤقتات # 4اكتب نصًا برمجيًا يعرض رسالة "Hello World" بنفس بنية التأخير كما في المهمة رقم 3 ، ولكن هذه المرة في مجموعات من 5 رسائل ، وستكون للمجموعة فترة تأخير رئيسية. بالنسبة للمجموعة الأولى المكونة من 5 رسائل ، نختار التأخير الأولي البالغ 100 مللي ثانية ، للمدة التالية - 200 مللي ثانية ، للثالثة - 300 مللي ثانية وما إلى ذلك.
إليك كيفية عمل هذا النص البرمجي:
- عند 100 مللي ثانية ، يعرض البرنامج النصي "Hello World" لأول مرة ، ويفعل ذلك 5 مرات بفاصل زمني يزيد عن 100 مللي ثانية. ستظهر الرسالة الأولى بعد 100 مللي ثانية ، والثانية بعد 200 مللي ثانية ، إلخ.
- بعد أول 5 رسائل ، يجب أن يزيد البرنامج النصي التأخير الرئيسي بمقدار 200 مللي ثانية. وبالتالي ، سيتم عرض الرسالة السادسة بعد 500 مللي ثانية + 200 مللي ثانية (700 مللي ثانية) ، و7-900 مللي ثانية ، والرسالة الثامنة - بعد 1100 مللي ثانية ، وهكذا.
- بعد 10 رسائل ، يجب أن يزيد البرنامج النصي الفاصل الزمني للتأخير الرئيسي بمقدار 300 مللي ثانية. يجب عرض الرسالة الحادية عشرة بعد 500 مللي ثانية + 1000 مللي ثانية + 300 مللي ثانية (18000 مللي ثانية). يجب عرض الرسالة الثانية عشرة بعد 2100 مللي ثانية ، إلخ.
وفقا لهذا المبدأ ، يجب أن يعمل البرنامج إلى أجل غير مسمى.
قم بتضمين تأخير في الرسالة المعروضة. يجب أن تحصل على شيء مثل هذا (لا تعليق):
Hello World. 100 // 100
Hello World. 100 // 200
Hello World. 100 // 300
Hello World. 100 // 400
Hello World. 100 // 500
Hello World. 200 // 700
Hello World. 200 // 900
Hello World. 200 // 1100
...
القيود : يمكنك استخدام المكالمات فقط لـ
setInterval
(وليس
setTimeout
) و ONE
if
.
الحلنظرًا
setInterval
لا يمكننا العمل إلا مع مكالمات
setInterval
، فنحن هنا نحتاج إلى استخدام العودية وأيضًا زيادة تأخير مكالمة
setInterval
التالية. بالإضافة إلى ذلك ، نحتاج إلى
if
لتحقيق ذلك فقط بعد 5 مكالمات إلى هذه الوظيفة العودية.
فيما يلي حل ممكن:
let lastIntervalId, counter = 5; const greeting = delay => { if (counter === 5) { clearInterval(lastIntervalId); lastIntervalId = setInterval(() => { console.log('Hello World. ', delay); greeting(delay + 100); }, delay); counter = 0; } counter += 1; }; greeting(100);
شكرا لكل من قرأها.