{"id":"l-js-4-5","courseId":"javascript-fundamentals","lang":"ar","kind":"lesson","summary":"تشرح لماذا تنتظر JavaScript دون أن تتجمّد، وتقرأ وتكتب كودًا مبنيًا على الـ promises باستخدام then وasync/await، وتستدعي fetch مع معالجة سليمة للأخطاء، وتتوقّع ترتيب المخرجات اعتمادًا على حلقة الأحداث (event loop).","html":"<p>سيكون Pocket أنفع لو استطاع أن يعرض إنفاقك باليورو أيضًا، وهذا يعني أن يطلب من خادم أسعار صرف اليوم. قد يستغرق هذا الطلب 50 ميلي ثانية أو خمس ثوانٍ. ولو توقّفت JavaScript ببساطة وانتظرت، لتجمّدت الصفحة كلها: لا تمرير، ولا كتابة، ولا نقرات. ولأن كودك يتشارك خيطًا (thread) واحدًا مع الصفحة نفسها، فهو يحتاج إلى طريقة يبدأ بها العمل البطيء، ويواصل، ثم يلتقط النتيجة لاحقًا. وهذا هو الكود <strong>غير المتزامن</strong> (asynchronous).</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">الـ Callbacks: “اتصل بي حين تنتهي”</h2>\n<p>أقدم أشكال “لاحقًا” هو الـ callback. تطلب <code dir=\"ltr\">setTimeout</code> من المتصفّح أن يستدعي دالة بعد مهلة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Requesting rates…\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setTimeout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Rates arrived\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">100</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Still responsive\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>يُطبع “Still responsive” قبل “Rates arrived”. <code dir=\"ltr\">setTimeout</code> لا توقف شيئًا؛ إنها تسلّم الدالة إلى المتصفّح وتعود فورًا. الـ callbacks تعمل، لكن حين تحتاج الخطوة الثانية إلى نتيجة الأولى، والثالثة إلى نتيجة الثانية، فإنها تتداخل أعمق فأعمق، ويجب تكرار معالجة الأخطاء في كل مستوى. وقد أُضيفت الـ promises لإصلاح ذلك.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">الـ Promises: قيمة تصل لاحقًا</h2>\n<p>الـ <strong>promise</strong> (الوعد) كائن يمثّل نتيجة ليست جاهزة بعد. يبدأ <strong>معلّقًا</strong> (pending)، ويستقرّ مرة واحدة بالضبط: إمّا <strong>مُنجَزًا</strong> (fulfilled) بقيمة، أو <strong>مرفوضًا</strong> (rejected) بخطأ. وتُلحق ما يجب أن يحدث بعد ذلك باستخدام <code dir=\"ltr\">.then</code> (عند النجاح) و<code dir=\"ltr\">.catch</code> (عند الفشل).</p>\n<p>البيئة المعزولة لهذه الدورة لا شبكة فيها، لذلك تستخدم الأمثلة <code dir=\"ltr\">fakeFetch</code>، وهي بديل يتصرّف مثل <code dir=\"ltr\">fetch</code> الحقيقية: تُرجع promise لكائن استجابة فيه <code dir=\"ltr\">ok</code> و<code dir=\"ltr\">status</code> ودالة <code dir=\"ltr\">json()</code>.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fakeFetch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    setTimeout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (url </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"/api/rates\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">        resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ ok: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">true</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, status: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">200</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ EUR: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.86</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, GBP: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.75</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">else</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">        resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ ok: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">false</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, status: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">404</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ({ error: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Not found\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }) });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">50</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">fakeFetch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/api/rates\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">then</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">response</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> response.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">())</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">then</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">rates</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"EUR rate:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, rates.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">EUR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">))</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  .</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Failed:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, error.message));</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>كل <code dir=\"ltr\">.then</code> تُرجع promise جديدًا، لذلك تتسلسل الخطوات في خط مسطّح بدلًا من أن تتداخل، و<code dir=\"ltr\">.catch</code> واحدة في النهاية تتعامل مع الفشل في أي خطوة. نادرًا ما ستُنشئ promises بنفسك باستخدام <code dir=\"ltr\">new Promise</code>؛ فالمكتبات وواجهات المتصفّح البرمجية تسلّمها إليك، ومهمتك أن تستهلكها.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">async وawait: promises تُقرأ مثل الكود العادي</h2>\n<p><code dir=\"ltr\">async</code>/<code dir=\"ltr\">await</code> طريقة أوضح لكتابة الشيء نفسه. داخل دالة موسومة بـ <code dir=\"ltr\">async</code>، توقف <code dir=\"ltr\">await promise</code> <strong>تلك الدالة</strong> مؤقتًا حتى يستقرّ الـ promise، ثم تعطيك قيمته. وتصبح الأخطاء استثناءات عادية تلتقطها بـ <code dir=\"ltr\">try</code>/<code dir=\"ltr\">catch</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"2\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fakeFetch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    setTimeout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> ok</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> url </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"/api/rates\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> body</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ok </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { EUR: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0.86</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { error: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Not found\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> };</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">      resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">({ ok, status: ok </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">?</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 200</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> :</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 404</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">: </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> body });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    }, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">50</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  });</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getJson</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> response</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fakeFetch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(url);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response.ok) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`HTTP ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">status</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} for ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> response.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> showRates</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  try</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> rates</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getJson</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/api/rates\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"EUR:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, rates.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">EUR</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    await</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getJson</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/api/missing\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (error) {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Could not load:\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, error.message);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">showRates</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>لاحظ ما <strong>لا</strong> تفعله <code dir=\"ltr\">await</code>: إنها لا تجمّد الصفحة. فبينما تنتظر <code dir=\"ltr\">showRates</code>، يواصل المتصفّح التعامل مع النقرات ورسم الإطارات. هذه الدالة وحدها هي المتوقّفة مؤقتًا.</p>\n<p>الدالة <code dir=\"ltr\">async</code> تُرجع دائمًا promise، حتى حين تُرجع بـ <code dir=\"ltr\">return</code> قيمة عادية. لذلك يجب على من يستدعيها أن يستخدم <code dir=\"ltr\">await</code> أيضًا، أو <code dir=\"ltr\">.then</code>.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>نسيان await</span></p>\n<p><code dir=\"ltr\">const rates = getJson(&quot;/api/rates&quot;); console.log(rates.EUR);</code> تطبع <code dir=\"ltr\">undefined</code>، لأن <code dir=\"ltr\">rates</code> promise معلّق، لا البيانات. وطباعة <code dir=\"ltr\">rates</code> نفسها تُظهر <code dir=\"ltr\">Promise { &lt;pending&gt; }</code>، وهذا ما يفضح الأمر. أضف <code dir=\"ltr\">await</code> (داخل دالة <code dir=\"ltr\">async</code>).</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">fetch الحقيقية</h2>\n<p>في صفحة حقيقية، تستبدل <code dir=\"ltr\">fakeFetch</code> بـ <code dir=\"ltr\">fetch</code> الخاصة بالمتصفّح، ويبقى شكل الكود كما هو:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">rates.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> getJson</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">url</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> response</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> fetch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(url);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">!</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response.ok) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`HTTP ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">response</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">status</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} for ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">url</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  }</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> response.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">json</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>فحص <code dir=\"ltr\">response.ok</code> ضروري، وكثير من الشروحات تُغفله. <code dir=\"ltr\">fetch</code> لا تُرفض إلا حين يتعذّر حدوث الطلب أصلًا، مثلًا حين يكون المستخدم غير متّصل. أما الـ 404 أو الـ 500 فاستجابة HTTP سليمة تمامًا، لذلك <strong>يُنجَز</strong> الـ promise، و<code dir=\"ltr\">ok</code> تساوي <code dir=\"ltr\">false</code>. ودون هذا الفحص، سيحاول كودك قراءة أسعار الصرف من صفحة خطأ.</p>\n<p>وحين لا يعتمد طلبان أحدهما على الآخر، ابدأهما معًا وانتظر مرة واحدة باستخدام <code dir=\"ltr\">Promise.all</code> (داخل دالة <code dir=\"ltr\">async</code>، كأي <code dir=\"ltr\">await</code>)، التي تُنجَز بمصفوفة من النتائج، أو تُرفض بمجرّد أن يفشل أيّ منهما:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">rates.js</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">rates</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">categories</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> await</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">all</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  getJson</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/api/rates\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  getJson</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"/api/categories\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">),</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);</span></span></code></pre></div>\n<h2 id=\"sec-5\" tabindex=\"-1\">حلقة الأحداث</h2>\n<p>كيف يستطيع خيط واحد فعل كل هذا؟ ينفّذ المحرّك كودك على <strong>مكدّس الاستدعاءات</strong> (call stack)، دالة واحدة في كل مرة. أما الأشياء البطيئة، مثل المؤقّتات وطلبات الشبكة، فيتولّاها المتصفّح خارج المكدّس. وحين ينتهي أحدها، يوضع الـ callback الخاص به في طابور. وكلما فرغ المكدّس، تأخذ <strong>حلقة الأحداث</strong> (event loop) الـ callback التالي من طابور وتنفّذه.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 260\" role=\"img\" aria-labelledby=\"l-js-4-5-t1\"><defs><marker id=\"arrow-l-js-4-5-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-js-4-5-t1\">يُنفَّذ كودك على مكدّس الاستدعاءات. يتولّى المتصفّح المؤقّتات وfetch، ويضع الـ callbacks المنتهية في طوابير. callbacks الـ promises تذهب إلى طابور المهام الدقيقة، والمؤقّتات والأحداث إلى طابور المهام. وحين يفرغ المكدّس، تنفّذ حلقة الأحداث أولًا كل المهام الدقيقة، ثم مهمة واحدة.</title>\n  <rect class=\"d-box-primary\" x=\"20\" y=\"30\" width=\"170\" height=\"170\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"105\" y=\"56\" text-anchor=\"middle\">مكدّس الاستدعاءات</text>\n  <rect class=\"d-box\" x=\"40\" y=\"74\" width=\"130\" height=\"34\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"105\" y=\"96\" text-anchor=\"middle\">showRates()</text>\n  <rect class=\"d-box\" x=\"40\" y=\"116\" width=\"130\" height=\"34\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"105\" y=\"138\" text-anchor=\"middle\">main script</text>\n  <rect class=\"d-box-accent\" x=\"250\" y=\"30\" width=\"170\" height=\"80\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"335\" y=\"58\" text-anchor=\"middle\">المتصفّح</text>\n  <text class=\"d-label\" x=\"335\" y=\"84\" text-anchor=\"middle\">المؤقّتات، fetch</text>\n  <rect class=\"d-box-success\" x=\"480\" y=\"30\" width=\"180\" height=\"70\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"570\" y=\"56\" text-anchor=\"middle\">المهام الدقيقة</text>\n  <text class=\"d-label\" x=\"570\" y=\"80\" text-anchor=\"middle\">callbacks الـ promises</text>\n  <rect class=\"d-box-warn\" x=\"480\" y=\"140\" width=\"180\" height=\"70\" rx=\"12\"/>\n  <text class=\"d-label-strong\" x=\"570\" y=\"166\" text-anchor=\"middle\">المهام</text>\n  <text class=\"d-label\" x=\"570\" y=\"190\" text-anchor=\"middle\">المؤقّتات، النقرات</text>\n  <path class=\"d-arrow\" d=\"M190 70 L246 70\" marker-end=\"url(#arrow-l-js-4-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 60 L476 60\" marker-end=\"url(#arrow-l-js-4-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M420 90 L476 160\" marker-end=\"url(#arrow-l-js-4-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M480 90 Q330 150 194 150\" marker-end=\"url(#arrow-l-js-4-5-1)\"/>\n  <path class=\"d-arrow\" d=\"M480 190 Q330 235 194 185\" marker-end=\"url(#arrow-l-js-4-5-1)\"/>\n  <text class=\"d-label-muted\" x=\"335\" y=\"142\" text-anchor=\"middle\">1. كل المهام الدقيقة</text>\n  <text class=\"d-label-muted\" x=\"335\" y=\"240\" text-anchor=\"middle\">2. ثم مهمة واحدة</text>\n</svg><figcaption>حلقة الأحداث تنفّذ الـ callbacks المنتظرة حين يفرغ المكدّس</figcaption></figure>\n<p>هناك طابوران، والترتيب بينهما يفسّر ألغازًا كثيرة. callbacks الـ promises تذهب إلى <strong>طابور المهام الدقيقة</strong> (microtask queue)؛ والمؤقّتات والأحداث تذهب إلى <strong>طابور المهام</strong> (task queue). وفي كل مرة يفرغ فيها المكدّس، تنفّذ الحلقة أولًا <strong>كل</strong> مهمة دقيقة منتظرة، ثم مهمة واحدة، ثم تفحص المهام الدقيقة من جديد:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"5\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"1: script\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setTimeout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"4: timeout task\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">then</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"3: promise microtask\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"2: script\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre><div class=\"run-output\" data-run-output=\"5\" hidden></div></div>\n<p>مهلة 0 ميلي ثانية لا تعني “الآن”؛ بل تعني “على شكل مهمة، بمجرّد أن يفرغ المكدّس وتنتهي المهام الدقيقة”. والنموذج نفسه يفسّر لماذا تجمّد حلقة تكرار طويلة الصفحة: فما دام كودك يشغل المكدّس، لا يستطيع أي معالج نقرات أو مؤقّت أو إعادة رسم أن يُنفَّذ.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>اعرض حالتي التحميل والخطأ</span></p>\n<p>لكل طلب شبكة ثلاث نتائج يستطيع المستخدم رؤيتها: الانتظار، والنجاح، والفشل. اعرض رسالة “Loading…” قبل الـ <code dir=\"ltr\">await</code>، واستبدلها بالبيانات عند النجاح، واعرض رسالة مفيدة في الـ <code dir=\"ltr\">catch</code>. التطبيق الذي لا يعرض شيئًا بصمت أثناء انتظاره، أو بعد فشله، يبدو معطّلًا.</p>\n</aside>\n<p>اكتمل القسم 4: يعيش Pocket في صفحة، ويتفاعل مع النقرات، ويستقبل المدخلات، ويحفظ البيانات، ويستطيع التحدّث إلى خادم. ويحوّل القسم 5 هذه الكومة المتنامية من الكود إلى برنامج منظّم جيدًا.</p>\n","toc":[{"id":"sec-1","text":"الـ Callbacks: “اتصل بي حين تنتهي”","level":2},{"id":"sec-2","text":"الـ Promises: قيمة تصل لاحقًا","level":2},{"id":"sec-3","text":"async وawait: promises تُقرأ مثل الكود العادي","level":2},{"id":"sec-4","text":"fetch الحقيقية","level":2},{"id":"sec-5","text":"حلقة الأحداث","level":2}],"takeaways":["تنفّذ JavaScript شيئًا واحدًا في كل مرة؛ أما العمل البطيء مثل طلبات الشبكة والمؤقّتات فيجري خارج كودك، ثم يُستدعى كودك راجعًا حين ينتهي.","الـ promise كائن يمثّل قيمة ستصل لاحقًا؛ وينتهي إمّا مُنجَزًا (fulfilled) بقيمة أو مرفوضًا (rejected) بخطأ.","<code dir=\"ltr\">await</code> توقف مؤقتًا الدالة <code dir=\"ltr\">async</code> المحيطة بها فقط حتى يستقرّ الـ promise؛ ضع الـ awaits داخل <code dir=\"ltr\">try</code>/<code dir=\"ltr\">catch</code> للتعامل مع حالات الرفض.","<code dir=\"ltr\">fetch</code> لا تُرفض إلا حين تفشل الشبكة؛ أما مع أخطاء HTTP مثل 404 فتُنجَز و<code dir=\"ltr\">response.ok</code> تساوي false، لذلك افحصها دائمًا.","بعد كل قطعة كود متزامن، تنفّذ حلقة الأحداث كل callbacks الـ promises المنتظرة قبل المؤقّت أو الحدث التالي."],"further":[{"title":"Using promises (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises"},{"title":"async function (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function"},{"title":"Using the Fetch API (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch"},{"title":"The event loop (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model"}],"quiz":[{"q":"<p>بأي ترتيب تُطبع الأسطر؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"A\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">setTimeout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"B\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">), </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">Promise</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">resolve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">().</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">then</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"C\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">));</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"D\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span></code></pre></div>\n","options":[{"text":"A، B، C، D","why":"هذا ترتيب كتابة الأسطر، لكن B وC دالتا callback تُنفَّذان لاحقًا، بعد كل الكود المتزامن."},{"text":"A، D، B، C","why":"كلتا الدالتين تنتظران الكود المتزامن، لكن callbacks الـ promises (المهام الدقيقة) تُنفَّذ قبل callbacks المؤقّتات."},{"text":"A، D، C، B","why":"صحيح. A وD تُنفَّذان الآن. ثم يُفرَّغ طابور المهام الدقيقة ©، وبعدها فقط تُنفَّذ مهمة المؤقّت (B)، حتى مع تأخير 0 ميلي ثانية."}],"answer":2},{"q":"<p><code dir=\"ltr\">const data = getRates(); console.log(data.EUR);</code> حيث <code dir=\"ltr\">getRates</code> دالة <code dir=\"ltr\">async</code>. ماذا يُطبع؟</p>","options":[{"text":"سعر صرف EUR، لأن دوال <code dir=\"ltr\">async</code> تُرجع قيمتها مباشرة.","why":"دالة <code dir=\"ltr\">async</code> تُرجع دائمًا promise. ودون <code dir=\"ltr\">await</code>، تكون <code dir=\"ltr\">data</code> ذلك الـ promise، لا الأسعار."},{"text":"<code dir=\"ltr\">undefined</code>، لأن <code dir=\"ltr\">data</code> هي promise معلّق، وليست له خاصية <code dir=\"ltr\">EUR</code>.","why":"صحيح. اكتب <code dir=\"ltr\">const data = await getRates();</code> داخل دالة async، أو استخدم <code dir=\"ltr\">.then</code>."},{"text":"يرمي خطأ، لأنه لا يمكنك استدعاء دالة async دون <code dir=\"ltr\">await</code>.","why":"يمكنك استدعاؤها؛ لكنك ستستعيد promise فقط. ونسيان <code dir=\"ltr\">await</code> يفشل بصمت، ولهذا هو خطأ شائع."}],"answer":1},{"q":"<p>يُرجع خادمك 404 Not Found للطلب <code dir=\"ltr\">fetch(&quot;/api/rates&quot;)</code>. ماذا يفعل الـ promise الخاص بـ fetch؟</p>","options":[{"text":"يُنجَز باستجابة قيمة <code dir=\"ltr\">ok</code> فيها false و<code dir=\"ltr\">status</code> فيها 404.","why":"صحيح. أخطاء HTTP ما زالت رحلات شبكة ناجحة ذهابًا وإيابًا. عليك أن تفحص <code dir=\"ltr\">response.ok</code> بنفسك وترمي خطأ إن أردت الرفض."},{"text":"يُرفض، فتُنفَّذ كتلة <code dir=\"ltr\">catch</code> لديك.","why":"<code dir=\"ltr\">fetch</code> لا تُرفض إلا مع أعطال على مستوى الشبكة مثل انقطاع الاتصال. والـ 404 استجابة HTTP صالحة."},{"text":"لا يستقرّ أبدًا، فيظل كودك ينتظر إلى الأبد.","why":"الخادم أجاب، فيستقرّ الـ promise. إنه يُنجَز باستجابة الـ 404."}],"answer":0},{"q":"<p>تحتاج إلى أسعار الصرف وقائمة الفئات، من رابطين مستقلّين، قبل العرض. أيّها الأسرع؟</p>","options":[{"text":"<code dir=\"ltr\">const r = await getRates(); const c = await getCategories();</code>","why":"هذا يعمل، لكن الطلب الثاني لا يبدأ إلا بعد انتهاء الأول، فتنتظر الاثنين واحدًا بعد الآخر."},{"text":"<code dir=\"ltr\">const [r, c] = await Promise.all([getRates(), getCategories()]);</code>","why":"صحيح. يبدأ الطلبان فورًا وتنتظر مرة واحدة، للأبطأ منهما."},{"text":"<code dir=\"ltr\">const r = getRates(); const c = getCategories();</code> دون await.","why":"يبدأ الاثنان بالتوازي، لكن <code dir=\"ltr\">r</code> و<code dir=\"ltr\">c</code> promises لا بيانات، فيفشل العرض باستخدامهما."}],"answer":1}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// A stand-in for fetch: the sandbox has no network. It returns a promise\n// of a response with ok, status and json(), after a short delay.\nfunction fakeFetch(url) {\n  const routes = {\n    \"/api/rates\": { EUR: 0.86, GBP: 0.75 },\n    \"/api/categories\": [{ name: \"food\" }, { name: \"travel\" }, { name: \"fun\" }],\n  };\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      const ok = url in routes;\n      const body = ok ? routes[url] : { error: \"Not found\" };\n      resolve({ ok, status: ok ? 200 : 404, json: async () => body });\n    }, 30);\n  });\n}\n\n// Return the parsed JSON body, or throw an Error mentioning the status\nasync function getJson(url) {\n  const response = fakeFetch(url);\n  return response.json();\n}\n\n// [\"food\", \"travel\", \"fun\"], from /api/categories\nasync function loadCategoryNames() {\n  const categories = getJson(\"/api/categories\");\n  return categories.map((c) => c.name);\n}\n\n// The amount converted to euro cents (rounded), using /api/rates\nasync function toEuroCents(cents) {\n}\n\n// The JSON from url, or `fallback` if anything goes wrong\nasync function safeGetJson(url, fallback) {\n  return getJson(url);\n}\n"},"solution":{"js":"// A stand-in for fetch: the sandbox has no network. It returns a promise\n// of a response with ok, status and json(), after a short delay.\nfunction fakeFetch(url) {\n  const routes = {\n    \"/api/rates\": { EUR: 0.86, GBP: 0.75 },\n    \"/api/categories\": [{ name: \"food\" }, { name: \"travel\" }, { name: \"fun\" }],\n  };\n  return new Promise((resolve) => {\n    setTimeout(() => {\n      const ok = url in routes;\n      const body = ok ? routes[url] : { error: \"Not found\" };\n      resolve({ ok, status: ok ? 200 : 404, json: async () => body });\n    }, 30);\n  });\n}\n\nasync function getJson(url) {\n  const response = await fakeFetch(url);\n  if (!response.ok) {\n    throw new Error(`HTTP ${response.status} for ${url}`);\n  }\n  return response.json();\n}\n\nasync function loadCategoryNames() {\n  const categories = await getJson(\"/api/categories\");\n  return categories.map((c) => c.name);\n}\n\nasync function toEuroCents(cents) {\n  const rates = await getJson(\"/api/rates\");\n  return Math.round(cents * rates.EUR);\n}\n\nasync function safeGetJson(url, fallback) {\n  try {\n    return await getJson(url);\n  } catch {\n    return fallback;\n  }\n}\n\nloadCategoryNames().then((names) => console.log(names));\n"},"checks":[{"id":"get-json","test":"const rates = await getJson('/api/rates');\nreturn rates && rates.EUR === 0.86;\n"},{"id":"get-json-error","test":"try { await getJson('/api/missing'); return false; } catch (e) { return e instanceof Error && e.message.includes('404'); }\n"},{"id":"names","test":"const names = await loadCategoryNames();\nreturn Array.isArray(names) && names.join() === 'food,travel,fun';\n"},{"id":"euros","test":"return (await toEuroCents(1000)) === 860 && (await toEuroCents(450)) === 387;\n"},{"id":"safe","test":"const fallback = [];\nconst bad = await safeGetJson('/api/nope', fallback);\nconst good = await safeGetJson('/api/rates', null);\nreturn bad === fallback && good && good.GBP === 0.75;\n"}],"title":"حمّل البيانات بشكل غير متزامن","prompt":"<p>البيئة المعزولة لا شبكة فيها، لذلك يعطيك كود البداية <code dir=\"ltr\">fakeFetch(url)</code>، التي تتصرّف مثل <code dir=\"ltr\">fetch</code>: تُرجع promise لاستجابة فيها <code dir=\"ltr\">ok</code> و<code dir=\"ltr\">status</code> ودالة <code dir=\"ltr\">json()</code> غير متزامنة. اكتب أربع دوال <code dir=\"ltr\">async</code>:</p>\n<ul>\n<li><code dir=\"ltr\">getJson(url)</code> تُرجع جسم الاستجابة بعد تحليله، و<strong>ترمي</strong> <code dir=\"ltr\">Error</code> تتضمّن رسالته رمز الحالة (مثل <code dir=\"ltr\">404</code>) حين تكون <code dir=\"ltr\">response.ok</code> تساوي false.</li>\n<li><code dir=\"ltr\">loadCategoryNames()</code> تُرجع <code dir=\"ltr\">[&quot;food&quot;, &quot;travel&quot;, &quot;fun&quot;]</code>، أي الخاصية <code dir=\"ltr\">name</code> لكل عنصر من <code dir=\"ltr\">/api/categories</code>.</li>\n<li><code dir=\"ltr\">toEuroCents(cents)</code> تُرجع المبلغ محوّلًا بسعر <code dir=\"ltr\">EUR</code> من <code dir=\"ltr\">/api/rates</code>، مقرّبًا إلى سنتات صحيحة.</li>\n<li><code dir=\"ltr\">safeGetJson(url, fallback)</code> تُرجع الـ JSON من <code dir=\"ltr\">url</code>، أو <code dir=\"ltr\">fallback</code> إن فشل التحميل.</li>\n</ul>\n","hints":["كود البداية ينسى <code dir=\"ltr\">await</code>: <code dir=\"ltr\">fakeFetch(url)</code> تعطي promise، والـ promise ليست له دالة <code dir=\"ltr\">json</code>. اكتب <code dir=\"ltr\">const response = await fakeFetch(url);</code>.","بعد الانتظار، افحص <code dir=\"ltr\">if (!response.ok) { throw new Error(`HTTP ${response.status}`); }</code> قبل إرجاع <code dir=\"ltr\">response.json()</code>.","تحتاج <code dir=\"ltr\">safeGetJson</code> إلى <code dir=\"ltr\">try { return await getJson(url); } catch { return fallback; }</code>. والـ <code dir=\"ltr\">await</code> داخل <code dir=\"ltr\">try</code> مهمة: دونها يفلت الرفض من الـ <code dir=\"ltr\">catch</code>."],"explanation":"<p>كل دالة تنتظر شيئًا هي <code dir=\"ltr\">async</code> وتستخدم <code dir=\"ltr\">await</code>، فيُقرأ الكود من الأعلى إلى الأسفل مع أن كل خطوة تنتهي لاحقًا. تحوّل <code dir=\"ltr\">getJson</code> خطأ HTTP إلى <code dir=\"ltr\">Error</code> مرميّ، لأن <code dir=\"ltr\">fetch</code> (وهذه النسخة المزيّفة) تُنجَز بنجاح مع الـ 404؛ وبعد ذلك لا يحتاج المُستدعون إلا إلى التعامل مع نوع واحد من الفشل. وتقرّب <code dir=\"ltr\">toEuroCents</code> بعد التحويل، محافظةً على قاعدة Pocket في السنتات الصحيحة: 450 × 0.86 تساوي 387.0، و1000 × 0.86 تساوي 860. وفي <code dir=\"ltr\">safeGetJson</code>، فإن <code dir=\"ltr\">return await</code> داخل <code dir=\"ltr\">try</code> مقصودة: فالانتظار هناك يجعل الرفض يُرمى داخل <code dir=\"ltr\">try</code>، حيث تستطيع <code dir=\"ltr\">catch</code> تحويله إلى القيمة البديلة.</p>\n","checkLabels":{"get-json":"<code dir=\"ltr\">getJson</code> تُرجع الجسم بعد تحليله","get-json-error":"<code dir=\"ltr\">getJson</code> ترمي Error تذكر 404 مع الرابط المفقود","names":"<code dir=\"ltr\">loadCategoryNames</code> تُرجع أسماء الفئات","euros":"<code dir=\"ltr\">toEuroCents</code> تحوّل بسعر EUR وتقرّب","safe":"<code dir=\"ltr\">safeGetJson</code> تُرجع القيمة البديلة حين يفشل التحميل"}},"words":722,"readMinutes":4}