
ما هو عدم التزامن؟ باختصار ، يعني عدم التزامن أداء العديد من المهام خلال فترة زمنية محددة. PHP يعمل في موضوع واحد ، مما يعني أنه لا يمكن تنفيذ سوى جزء واحد من كود PHP في أي وقت. قد يبدو هذا بمثابة قيود ، لكنه يمنحنا في الواقع مزيدًا من الحرية. نتيجةً لذلك ، لا يتعين علينا مواجهة كل التعقيدات المرتبطة بالبرمجة متعددة مؤشرات الترابط. ولكن من ناحية أخرى ، هناك مجموعة من المشاكل. علينا أن نتعامل مع عدم التزامن. نحن بحاجة إلى إدارته بطريقة ما وتنسيقه.
تقديم ترجمة مقال من مدونة Skyeng الخلفية المطور سيرجي جوك.
على سبيل المثال ، عندما نقوم بتنفيذ طلبين متوازيين HTTP ، فإننا نقول أنها "تعمل بالتوازي". هذا عادة ما يكون سهلاً وبسيطًا ، ولكن تنشأ المشاكل عندما نحتاج إلى تبسيط استجابات هذه الطلبات ، على سبيل المثال ، عندما يتطلب أحد الطلبات بيانات مستلمة من طلب آخر. وبالتالي ، فإن الصعوبة الأكبر تكمن في الإدارة غير المتزامنة. هناك عدة طرق مختلفة لحل هذه المشكلة.
لا يوجد حاليًا أي دعم مضمن للتجريدات عالية المستوى لإدارة عدم التزامن في PHP ، وعلينا استخدام مكتبات الطرف الثالث مثل ReactPHP و Amp. في الأمثلة في هذه المقالة ، أستخدم ReactPHP.
PROMIS
لفهم فكرة الوعود بشكل أفضل ، سيكون مثال الحياة الواقعية مفيدًا. تخيل أنك في ماكدونالدز وتريد أن تضع طلبية. أنت تدفع المال مقابل ذلك ، وبالتالي تبدأ الصفقة. استجابة لهذه الصفقة ، تتوقع الحصول على الهامبرغر والبطاطا المقلية. لكن أمين الصندوق لا يعيد الطعام على الفور. بدلاً من ذلك ، تتلقى شيكًا برقم الطلب. النظر في هذا الاختيار باعتباره وعدا للنظام في المستقبل. يمكنك الآن إجراء هذا الاختيار والبدء في التفكير في وجبة الغداء اللذيذة. همبرغر والبطاطا المقلية المتوقعة ليست جاهزة بعد ، لذلك تقف وتنتظر حتى يتم الانتهاء من طلبك. بمجرد ظهور رقمه على الشاشة ، ستستبدل شيك طلبك. هذه هي الوعود:
بديلا عن القيمة المستقبلية.
الوعد هو تمثيل لمعنى مستقبلي ، غلاف مستقل عن الزمن نلتف حوله. لا يهمنا إذا كانت القيمة موجودة بالفعل أم لا. ما زلنا نفكر به بنفس الطريقة. تخيل أن لدينا ثلاثة طلبات HTTP غير متزامنة يتم تنفيذها "بالتوازي" ، بحيث يتم إكمالها في وقت واحد تقريبًا. لكننا نريد تنسيق إجاباتهم بطريقة أو بأخرى. على سبيل المثال ، نريد طباعة هذه الإجابات بمجرد تلقيها ، ولكن مع وجود قيود طفيفة واحدة: لا تقم بطباعة الإجابة الثانية حتى يتم تلقي الأولى. هنا أعني أنه إذا تم الوفاء بالوعد $ 1 ، فإننا نطبعه. ولكن إذا تم الوفاء بالوعد دولار 2 أولاً ، فلن نقوم بطباعته ، لأن الوعد $ 1 لا يزال قيد التقدم. تخيل أننا نحاول تكييف ثلاثة استعلامات تنافسية بطريقة تبدو للمستخدم النهائي وكأنها طلب سريع واحد.
لذا ، كيف يمكننا حل هذه المشكلة بالوعود؟ بادئ ذي بدء ، نحن بحاجة إلى وظيفة تعطي وعدًا. يمكننا جمع ثلاثة مثل هذه الوعود ثم وضعها معا. إليك بعض الرموز المزيفة لهذا:
<?php use React\Promise\Promise; function fakeResponse(string $url, callable $callback) { $callback("response for $url"); } function makeRequest(string $url) { return new Promise(function(callable $resolve) use ($url) { fakeResponse($url, $resolve); }); }
هنا لدي وظيفتان:
يحتوي fakeResponse (سلسلة $ url ، callback $ callable) على استجابة مشفرة بشكل ثابت ويسمح رد الاتصال المحدد مع هذه الإجابة ؛
تقوم makeRequest (string $ url) بإرجاع وعد يستخدم fakeResponse () للإشارة إلى إكمال الطلب.
من رمز العميل ، ندعو ببساطة الدالة makeRequest () والحصول على الوعود:
<?php $promise1 = makeRequest('url1'); $promise2 = makeRequest('url2'); $promise3 = makeRequest('url3');
كان الأمر بسيطًا ، لكننا نحتاج الآن إلى فرز هذه الإجابات بطريقة أو بأخرى. مرة أخرى ، نريد أن يتم طباعة الرد من الوعد الثاني فقط بعد الانتهاء من الأول. لحل هذه المشكلة ، يمكنك بناء سلسلة من الوعود:
<?php $promise1 ->then('var_dump') ->then(function() use ($promise2) { return $promise2; }) ->then('var_dump') ->then(function () use ($promise3) { return $promise3; }) ->then('var_dump') ->then(function () { echo 'Complete'; });
في الكود أعلاه ، نبدأ بـ $ وعد 1 . بمجرد اكتماله ، نطبع قيمته. نحن لا نهتم بالوقت الذي يستغرقه: أقل من ثانية أو ساعة. بمجرد اكتمال الوعد ، سنطبع قيمته. ثم ننتظر وعد $ 2 . وهنا يمكن أن يكون لدينا سيناريوهان:
لقد اكتمل الوعد 2 دولار ، وقمنا على الفور بطباعة قيمته ؛
لا يزال الوفاء بوعد 2 $ ، ونحن في انتظار.
بفضل تسلسل الوعود ، لم نعد بحاجة إلى القلق بشأن ما إذا كان قد تم الوفاء ببعض الوعد أم لا. لا يعتمد Promis على الوقت ، وبالتالي فإنه يخفي حالاته منا (في هذه العملية ، تم إكماله أو إلغاؤه بالفعل).
هذه هي الطريقة التي يمكنك بها التحكم في عدم التزامن بالوعود. تبدو رائعة ، سلسلة الوعود أجمل بكثير ومفهومة أكثر من مجموعة من عمليات الاسترجاعات المتداخلة.
مولدات
في PHP ، تم تصميم المولدات في دعم لغوي للوظائف التي يمكن إيقافها مؤقتًا ثم الاستمرار فيها. عندما يتوقف تنفيذ التعليمات البرمجية داخل هذا المولد ، يبدو برنامجًا محظورًا صغيرًا. ولكن خارج هذا البرنامج ، خارج المولد ، كل شيء آخر يواصل العمل. هذا هو كل سحر وقوة المولدات.
يمكننا حرفيا إيقاف المولد محليًا لانتظار اكتمال الوعد. الفكرة الأساسية هي استخدام الوعود والمولدات معًا. يأخذون السيطرة على عدم التزامن ، ونحن ندعو فقط العائد عندما نحتاج إلى تعليق المولد. إليك البرنامج نفسه ، لكننا الآن نربط المولدات والوعود:
<?php use Recoil\React\ReactKernel;
بالنسبة لهذا الرمز ، أستخدم مكتبة recoilphp / recoil ، والتي تتيح لك الاتصال بـ ReactKernel :: start () . يجعل الارتداد من الممكن استخدام مولدات PHP لتنفيذ وعود ReactPHP غير المتزامنة.
هنا ، ما زلنا نقوم بثلاثة استعلامات بالتوازي ، لكن الآن نقوم بفرز الردود باستخدام الكلمة المفتاحية ذات العائد . ومرة أخرى ، نعرض النتائج في نهاية كل وعد ، ولكن بعد الوعد السابق.
Korutiny
Coroutines هي وسيلة لتقسيم العملية أو العملية إلى أجزاء ، مع بعض التنفيذ داخل كل قطعة. نتيجةً لذلك ، اتضح أنه بدلاً من إجراء العملية بأكملها في وقت واحد (مما قد يؤدي إلى تجميد ملحوظ للتطبيق) ، سيتم تنفيذه تدريجياً حتى يتم الانتهاء من كل ما يلزم من العمل.
الآن بعد أن أصبح لدينا مولدات قابلة للتجديد ومتجددة ، يمكننا استخدامها لكتابة التعليمات البرمجية غير المتزامنة مع الوعود في شكل متزامن أكثر دراية. باستخدام مولدات وعود PHP ، يمكنك التخلص تمامًا من عمليات الاسترجاعات. الفكرة هي أنه عندما نعطي وعدًا (باستخدام استدعاء العائد) ، يشترك coroutine في ذلك. توقف Corutin وينتظر حتى يكتمل الوعد (مكتمل أو ملغى). بمجرد اكتمال الوعد ، ستواصل coroutine الوفاء. عند الانتهاء بنجاح ، يرسل وعد coroutine القيمة المستلمة إلى سياق المولد باستخدام استدعاء Generator :: send ($ value) . إذا فشل الوعد ، يلقي Corutin استثناءًا خلال المولد باستخدام استدعاء Generator :: throw () . في غياب عمليات الاسترجاعات ، يمكننا كتابة تعليمات برمجية غير متزامنة تشبه الكود المتزامن المعتاد تقريبًا.
التنفيذ المتسلسل
عند استخدام coroutine ، أصبح أمر التنفيذ في التعليمات البرمجية غير المتزامنة مهمًا الآن. يتم تنفيذ الكود تمامًا إلى المكان الذي يتم فيه استدعاء الكلمة الرئيسية ذات العائد ثم يتم إيقافها مؤقتًا حتى يتم الانتهاء من الوعد. النظر في التعليمات البرمجية التالية:
<?php use Recoil\React\ReactKernel;
Promise1: سيتم عرضه هنا ، ثم يتوقف التنفيذ وينتظره . بمجرد اكتمال الوعد من makeRequest ('url1') ، نطبع نتيجته وننتقل إلى السطر التالي من التعليمات البرمجية.
خطأ في التعامل
ينص معيار Promises / A + Promise على أن كل وعد يحتوي على أساليب () و catch () . تتيح لك هذه الواجهة بناء سلاسل من الوعود والتقاط الأخطاء اختياريًا. النظر في التعليمات البرمجية التالية:
<?php operation()->then(function ($result) { return anotherOperation($result); })->then(function ($result) { return yetAnotherOperation($result); })->then(function ($result) { echo $result; });
هنا لدينا سلسلة من الوعود التي تنقل نتيجة كل وعد سابق إلى التالي. ولكن لا يوجد كتلة catch () في هذه السلسلة ، لا يوجد معالجة خطأ. عند فشل الوعد في السلسلة ، ينتقل تنفيذ التعليمات البرمجية إلى أقرب معالج خطأ في السلسلة. في حالتنا ، هذا يعني أنه سيتم تجاهل الوعد البارز ، وأي أخطاء يتم إلقاؤها ستختفي إلى الأبد. مع coroutines ، تأتي معالجة الأخطاء في المقدمة. في حالة فشل أي عملية غير متزامنة ، سيتم طرح استثناء:
<?php use Recoil\React\ReactKernel; use React\Promise\RejectedPromise;
جعل رمز غير متزامن مقروءا
للمولدات تأثير جانبي مهم حقًا يمكننا استخدامه للسيطرة على عدم التزامن والذي يحل مشكلة قابلية قراءة الشفرة غير المتزامنة. من الصعب علينا أن نفهم كيف سيتم تنفيذ التعليمات البرمجية غير المتزامنة بسبب حقيقة أن مؤشر ترابط التنفيذ ينتقل باستمرار بين أجزاء مختلفة من البرنامج. ومع ذلك ، فإن دماغنا يعمل بشكل متزامن ومترابط. على سبيل المثال ، نخطط ليومنا بشكل متسق للغاية: القيام بواحد ، ثم آخر ، وهلم جرا. لكن الشفرة غير المتزامنة لا تعمل بالطريقة التي اعتادت بها أدمغتنا على التفكير. حتى سلسلة الوعود البسيطة قد لا تبدو قابلة للقراءة:
<?php $promise1 ->then('var_dump') ->then(function() use ($promise2) { return $promise2; }) ->then('var_dump') ->then(function () use ($promise3) { return $promise3; }) ->then('var_dump') ->then(function () { echo 'Complete'; });
علينا تفكيكها عقلياً من أجل فهم ما يحدث هناك. لذلك نحن بحاجة إلى نمط مختلف للتحكم في عدم التزامن. وباختصار ، توفر المولدات طريقة لكتابة التعليمات البرمجية غير المتزامنة بحيث تبدو متزامنة.
تجمع الوعود والمولدات بين أفضل ما في العالمين: نحصل على شفرة غير متزامنة مع أداء رائع ، ولكن في الوقت نفسه يبدو متزامنًا وخطيًا ومتسلسلاً. يتيح لك Coroutines إخفاء عدم التزامن ، الذي أصبح بالفعل تفاصيل تنفيذ. وكودنا في نفس الوقت يبدو أن عقولنا معتاد على التفكير - خطيًا وتتابعيًا.
إذا كنا نتحدث عن ReactPHP ، فيمكننا استخدام مكتبة RecoilPHP لكتابة الوعود في شكل coroutine. في أمبير ، تتوفر coroutines مباشرة من خارج منطقة الجزاء.