{"id":"l-js-5-3","courseId":"javascript-fundamentals","lang":"ar","kind":"lesson","summary":"تقرأ رسائل الأخطاء وآثار المكدّس (stack traces)، وترمي الأخطاء وتلتقطها عن قصد بأنواع أخطاء مخصّصة، وتجد الأخطاء البرمجية بشكل منهجي باستخدام نقاط التوقّف ومصحّح الأخطاء في DevTools.","html":"<p>كل مبرمج يكتب أخطاء برمجية كل يوم. وما يميّز المطوّرين ذوي الخبرة عن المبتدئين ليس قلّة الأخطاء، بل طريقة أهدأ وأسرع في العثور عليها. وهذا يبدأ بقراءة ما يقوله لك الحاسوب.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">قراءة الخطأ</h2>\n<p>حين تعجز JavaScript عن الاستمرار، فإنها <strong>ترمي</strong> (throw) خطأً، وإن لم يلتقطه شيء، يعرض الـ console شيئًا كهذا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"text\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Text</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>Uncaught TypeError: Cannot read properties of undefined (reading 'amount')\n    at totalCents (expenses.js:12:31)\n    at renderSummary (ui.js:40:17)\n    at HTMLFormElement.&lt;anonymous&gt; (main.js:27:5)</code></pre></div>\n<p>اقرأه في ثلاثة أجزاء. <strong>النوع</strong>، <code dir=\"ltr\">TypeError</code>، يقول ما صنف المشكلة: قيمة كان نوعها خاطئًا بالنسبة لما فعلته بها. و<strong>الرسالة</strong> تقول ماذا بالضبط: شيء ما قبل <code dir=\"ltr\">.amount</code> كان <code dir=\"ltr\">undefined</code>. و<strong>أثر المكدّس</strong> (stack trace) يسرد سلسلة استدعاءات الدوال التي أوصلت إلى هناك، الأحدث أولًا: فشلت <code dir=\"ltr\">totalCents</code> في السطر 12 من <code dir=\"ltr\">expenses.js</code>، وكانت قد استدعتها <code dir=\"ltr\">renderSummary</code>، التي استدعاها معالج الإرسال في النموذج. وفي DevTools، كل موقع منها رابط يأخذك مباشرة إلى ذلك السطر.</p>\n<p>الأنواع التي ستصادفها أكثر من غيرها:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>النوع</th>\n<th>يعني عادةً</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code dir=\"ltr\">ReferenceError</code></td>\n<td>اسم غير موجود: خطأ إملائي، أو استخدام قبل التصريح</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">TypeError</code></td>\n<td>نوع خاطئ من القيم: استدعاء شيء ليس دالة، أو القراءة من <code dir=\"ltr\">undefined</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">SyntaxError</code></td>\n<td>تعذّر تحليل الكود: قوس أو علامة تنصيص أو فاصلة مفقودة</td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">RangeError</code></td>\n<td>رقم خارج المسموح، مثل <code dir=\"ltr\">toFixed(200)</code></td>\n</tr>\n</tbody>\n</table></div><h2 id=\"sec-2\" tabindex=\"-1\">رمي الأخطاء عن قصد</h2>\n<p>يمكنك أن ترمي الأخطاء بنفسك، وينبغي أن تفعل حين يُعطى كودك شيئًا لا يستطيع التعامل معه بشكل معقول:</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\"> parseAmount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">text</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\"> dollars</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(text);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (text.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">trim</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ||</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> !</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">Number.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">isFinite</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(dollars) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">||</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> dollars </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">&#x3C;=</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:#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\">`Invalid amount: \"${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">text</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\"> Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(dollars </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</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\">}</span></span>\n<span class=\"line\"></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:#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:#8250DF;--shiki-dark:#D2A8FF\">parseAmount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"12.20\"</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:#8250DF;--shiki-dark:#D2A8FF\">parseAmount</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"twelve\"</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\">\"this line never runs\"</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\">\"Caught:\"</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">finally</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\">\"finally always runs\"</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 class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p><code dir=\"ltr\">throw</code> توقف الدالة فورًا وتصعد في مكدّس الاستدعاءات حتى تلتقطها كتلة <code dir=\"ltr\">try</code>/<code dir=\"ltr\">catch</code> ما. وإن لم تلتقطها أي كتلة، تصبح خطأً غير ملتقط في الـ console. أما <code dir=\"ltr\">finally</code> فتُنفَّذ سواء رُمي شيء أم لا، وهي المكان المناسب للتنظيف، مثل إخفاء مؤشّر التحميل.</p>\n<h3 id=\"sec-3\" tabindex=\"-1\">أنواع أخطاء مخصّصة</h3>\n<p>بعض الأخطاء متوقّعة، مثل أن يكتب المستخدم مبلغًا غير صالح؛ وبعضها أخطاء برمجية. ويحتاج المُستدعون إلى التمييز بينها، وصنف الخطأ المخصّص يجعل ذلك ممكنًا:</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\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ValidationError</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  constructor</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">field</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">message</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    super</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(message);</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.name </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"ValidationError\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">    this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.field </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> field;</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\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> addFromForm</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">label</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">amountText</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\"> (label.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">trim</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</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:#CF222E;--shiki-dark:#FF7B72\">    throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> ValidationError</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"label\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Enter what you spent money on.\"</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\"> { label: label.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">trim</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(), amount: Math.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">round</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">Number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(amountText) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</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\">}</span></span>\n<span class=\"line\"></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:#8250DF;--shiki-dark:#D2A8FF\">  addFromForm</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"  \"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"4.50\"</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:#CF222E;--shiki-dark:#FF7B72\">  if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (error </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">instanceof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ValidationError</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\">`Show next to ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">error</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">field</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}: ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">error</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">message</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 style=\"color:#CF222E;--shiki-dark:#FF7B72\">else</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    throw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> error; </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// not ours to handle</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></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p><code dir=\"ltr\">extends Error</code> تجعل <code dir=\"ltr\">ValidationError</code> نوعًا من <code dir=\"ltr\">Error</code> له أثر مكدّس، و<code dir=\"ltr\">super(message)</code> تنفّذ الـ constructor الخاص بالصنف الأب، والخاصية الإضافية <code dir=\"ltr\">field</code> تخبر النموذج أين يعرض الرسالة. وكتلة <code dir=\"ltr\">catch</code> لا تتعامل إلا مع ما تفهمه، و<strong>تعيد رمي</strong> كل ما عداه، فتظل الأخطاء البرمجية الحقيقية تطفو إلى السطح.</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>كتلة catch الفارغة</span></p>\n<p><code dir=\"ltr\">try { save(); } catch (e) {}</code> تجعل كل فشل غير مرئي، بما في ذلك الخطأ الذي ستُدخله الشهر القادم. إن التقطت خطأ، فافعل به شيئًا: اعرض رسالة، أو استخدم قيمة بديلة، أو على الأقل <code dir=\"ltr\">console.error(error)</code>. والتقط الخطأ أقرب ما يمكن إلى المكان الذي تستطيع فيه فعلًا معالجة المشكلة، ولا تلتقطه في أي مكان آخر.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">مصحّح الأخطاء: توقّف وانظر</h2>\n<p><code dir=\"ltr\">console.log</code> أداة أولى جيدة، لكنها لا تُريك إلا ما خطر لك أن تطبعه. أما <strong>مصحّح الأخطاء</strong> (debugger) في DevTools فيوقف برنامجك مؤقتًا عند سطر ما ويُريك كل شيء.</p>\n<p>في Chrome أو Edge، افتح DevTools، واذهب إلى لوحة <strong>Sources</strong>، وافتح ملفك، وانقر على رقم سطر. هذا يضع <strong>نقطة توقّف</strong> (breakpoint). وفي المرة التالية التي يوشك فيها هذا السطر أن يُنفَّذ، يتوقّف كل شيء مؤقتًا، وتستطيع:</p>\n<ul>\n<li>أن تمرّر المؤشّر فوق أي متغيّر لترى قيمته، أو تقرأها كلها في قسم <strong>Scope</strong>؛</li>\n<li>أن تقرأ قسم <strong>Call Stack</strong> لترى كيف وصلت إلى هنا؛</li>\n<li>أن <strong>تتخطّى</strong> (step over: نفّذ هذا السطر وتوقّف عند التالي)، أو <strong>تدخل</strong> (step into: اتبع استدعاء الدالة إلى داخلها)، أو <strong>تخرج</strong> (step out: أكمل هذه الدالة)؛</li>\n<li>أن تكتب أي تعبير في الـ Console أثناء التوقّف، مستخدمًا القيم المتوقّفة.</li>\n</ul>\n<p>انقر بالزر الأيمن على رقم سطر لتضيف <strong>نقطة توقّف مشروطة</strong>، مثل <code dir=\"ltr\">expense.amount &gt; 100000</code>، لا تتوقّف إلا حين يكون الشرط صحيحًا. ويمكنك أيضًا أن تكتب التعليمة <code dir=\"ltr\">debugger;</code> في كودك: فحين تكون DevTools مفتوحة، يتوقّف التنفيذ هناك. احذفها قبل أن تُجري commit.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 660 200\" role=\"img\" aria-labelledby=\"l-js-5-3-t1\"><defs><marker id=\"arrow-l-js-5-3-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-5-3-t1\">دورة من خمس خطوات: أعِد إنتاج الخطأ بشكل موثوق، واقرأ الخطأ أو صِف المخرجات الخاطئة، وضع فرضية، وافحص القيم بالطباعة أو بنقاط التوقّف، ثم أصلح وتحقّق، وكرّر إن بقي الخطأ.</title>\n  <rect class=\"d-box-accent\" x=\"10\" y=\"70\" width=\"110\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"65\" y=\"103\" text-anchor=\"middle\">أعِد الإنتاج</text>\n  <rect class=\"d-box\" x=\"140\" y=\"70\" width=\"110\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"195\" y=\"103\" text-anchor=\"middle\">اقرأ</text>\n  <rect class=\"d-box\" x=\"270\" y=\"70\" width=\"120\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"330\" y=\"103\" text-anchor=\"middle\">افترض</text>\n  <rect class=\"d-box-primary\" x=\"410\" y=\"70\" width=\"110\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"465\" y=\"103\" text-anchor=\"middle\">افحص</text>\n  <rect class=\"d-box-success\" x=\"540\" y=\"70\" width=\"110\" height=\"56\" rx=\"10\"/>\n  <text class=\"d-label\" x=\"595\" y=\"96\" text-anchor=\"middle\">أصلح</text>\n  <text class=\"d-label\" x=\"595\" y=\"114\" text-anchor=\"middle\">وتحقّق</text>\n  <path class=\"d-arrow\" d=\"M120 98 L136 98\" marker-end=\"url(#arrow-l-js-5-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M250 98 L266 98\" marker-end=\"url(#arrow-l-js-5-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M390 98 L406 98\" marker-end=\"url(#arrow-l-js-5-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M520 98 L536 98\" marker-end=\"url(#arrow-l-js-5-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M595 126 Q595 180 330 180 Q200 180 200 130\" marker-end=\"url(#arrow-l-js-5-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"420\" y=\"172\" text-anchor=\"middle\">ما زال معطّلًا: عُد إلى القراءة</text>\n</svg><figcaption>حلقة تصحيح تنجح مع كل خطأ برمجي</figcaption></figure>\n<h2 id=\"sec-5\" tabindex=\"-1\">طريقة تصلح لكل خطأ</h2>\n<ol>\n<li><strong>أعِد إنتاجه</strong> بشكل موثوق. “أحيانًا يكون المجموع خاطئًا” تصبح “بعد حذف المصروف الأول، يكون المجموع خاطئًا”.</li>\n<li><strong>اقرأ</strong> الخطأ، أو صِف بدقة ما توقّعته وما حصلت عليه.</li>\n<li><strong>افترض</strong>: ما الشيء الواحد الذي يفسّر ذلك؟</li>\n<li><strong>افحص</strong> القيم عند النقطة التي تشتبه بها، بنقطة توقّف أو بالطباعة، وجد أول مكان يختلف فيه الواقع عن توقّعك.</li>\n<li><strong>أصلح</strong> السبب هناك، ثم <strong>تحقّق</strong> بالخطوات نفسها بالضبط من الخطوة 1.</li>\n</ol>\n<p>والإغراء، خاصة حين تكون متعبًا، هو القفز مباشرة إلى تغيير الكود حتى يختفي العَرَض. وهذا يُنتج عادةً خطأً ثانيًا. عشر دقائق من النظر في القيم الحقيقية خير من ساعة من التخمين.</p>\n<p>صار لديك الآن كل قطعة يحتاجها Pocket. وفي الدرس التالي تجمعها في التطبيق المكتمل.</p>\n","toc":[{"id":"sec-1","text":"قراءة الخطأ","level":2},{"id":"sec-2","text":"رمي الأخطاء عن قصد","level":2},{"id":"sec-3","text":"أنواع أخطاء مخصّصة","level":3},{"id":"sec-4","text":"مصحّح الأخطاء: توقّف وانظر","level":2},{"id":"sec-5","text":"طريقة تصلح لكل خطأ","level":2}],"takeaways":["رسالة الخطأ تسمّي نوع الخطأ، وتقول ما الذي حدث، وأثر المكدّس (stack trace) الخاص بها يشير إلى الملف والسطر حيث وقع.","ارمِ <code dir=\"ltr\">Error</code> حين يعجز كودك عن الاستمرار بشكل معقول؛ والتقطه فقط حيث تستطيع فعل شيء مفيد، وأعِد رمي كل ما لم تتوقّعه.","صنف خطأ مخصّص (<code dir=\"ltr\">class ValidationError extends Error</code>) يتيح للمُستدعين التمييز بين المشاكل المتوقّعة والأخطاء البرمجية الحقيقية باستخدام <code dir=\"ltr\">instanceof</code>.","نقطة التوقّف (breakpoint) توقف كودك مؤقتًا عند سطر ما كي تفحص كل متغيّر وتتقدّم فيه سطرًا سطرًا؛ و<code dir=\"ltr\">debugger;</code> تضع واحدة من داخل الكود.","صحّح الأخطاء بالأدلة لا بالتخمين؛ أعِد إنتاج الخطأ، وجد أول سطر يختلف فيه الواقع عن توقّعك، ثم أصلحه."],"further":[{"title":"Control flow and error handling (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling"},{"title":"What went wrong? Troubleshooting JavaScript (MDN)","url":"https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Scripting/What_went_wrong"},{"title":"Error (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error"}],"quiz":[{"q":"<p><code dir=\"ltr\">TypeError: expenses.map is not a function</code> عند <code dir=\"ltr\">renderExpenses (ui.js:14)</code>. ما الخطوة الأولى الأنفع؟</p>","options":[{"text":"غلّف السطر 14 بـ <code dir=\"ltr\">try</code>/<code dir=\"ltr\">catch</code> كي تظل الصفحة تعمل.","why":"هذا يُخفي العَرَض ويترك السبب. وستتوقّف القائمة عن العرض بصمت."},{"text":"أعِد كتابة <code dir=\"ltr\">renderExpenses</code> بحلقة <code dir=\"ltr\">for</code> بدلًا من <code dir=\"ltr\">map</code>.","why":"الحلقة ستفشل أيضًا، أو ستتصرّف بغرابة. السؤال الحقيقي هو ما الذي تكونه <code dir=\"ltr\">expenses</code> فعلًا."},{"text":"أعِد تشغيل خادم التطوير وامسح ذاكرة التخزين المؤقت في المتصفّح.","why":"الرسالة تصف مشكلة منطقية، قيمة من نوع خاطئ، لا ملفًا قديمًا."},{"text":"توقّف عند السطر 14 من ui.js وافحص ما تحمله <code dir=\"ltr\">expenses</code> فعلًا، ثم تتبّع مصدر تلك القيمة.","why":"صحيح. <code dir=\"ltr\">map</code> مفقودة، إذن <code dir=\"ltr\">expenses</code> ليست مصفوفة، ربما <code dir=\"ltr\">null</code> قادمة من التخزين أو كائن. اكتشف أيّهما، ثم أصلح المصدر."}],"answer":3},{"q":"<p>كتلة <code dir=\"ltr\">catch</code> في دالة الحفظ في Pocket مكتوبة هكذا: <code dir=\"ltr\">catch (error) {}</code>. ما المشكلة؟</p>","options":[{"text":"أي فشل، بما في ذلك خطأ تافه كخطأ إملائي في كودك أنت، يختفي الآن دون أثر.","why":"صحيح. كتلة catch الفارغة تبتلع كل الأخطاء. تعامل مع ما تتوقّعه، واطبع الباقي أو أعِد رميه."},{"text":"يجب استخدام المعامل <code dir=\"ltr\">error</code> دائمًا وإلا فلن يعمل الكود.","why":"المعامل غير المستخدم مسموح. بل يمكنك كتابة <code dir=\"ltr\">catch {}</code>. المشكلة في التخلّص من الأخطاء بصمت."},{"text":"كتل <code dir=\"ltr\">catch</code> لا تعمل إلا داخل دوال <code dir=\"ltr\">async</code>.","why":"<code dir=\"ltr\">try</code>/<code dir=\"ltr\">catch</code> تعمل في أي دالة. ومع الكود غير المتزامن عليك فقط أن تستخدم <code dir=\"ltr\">await</code> داخل <code dir=\"ltr\">try</code>."}],"answer":0},{"q":"<p>تريد أن تعرف لماذا يكون المجموع خاطئًا فقط عند المصروف رقم 40 في حلقة. أيّ ميزة في DevTools أنسب؟</p>","options":[{"text":"<code dir=\"ltr\">console.log</code> قبل الحلقة.","why":"هذا يُنفَّذ مرة واحدة، قبل الجزء المهم. ولا يستطيع أن يُريك الدورة الأربعين."},{"text":"لوحة Network.","why":"لوحة Network تعرض الطلبات إلى الخوادم. وهذه عملية حسابية داخل كودك أنت."},{"text":"نقطة توقّف مشروطة داخل الحلقة، مثل <code dir=\"ltr\">i === 39</code>.","why":"صحيح. يتوقّف مصحّح الأخطاء في تلك الدورة فقط، وتستطيع فحص كل متغيّر في تلك اللحظة."},{"text":"إعادة تحميل الصفحة مع تعطيل ذاكرة التخزين المؤقت.","why":"التخزين المؤقت لا يغيّر طريقة تنفيذ عملية حسابية. تحتاج إلى النظر في القيم أثناء الحلقة."}],"answer":2}],"exercise":{"kind":"web","mode":"js","starter":{"js":"// 1. An error type for problems the user can fix\nclass ValidationError extends Error {\n  constructor(field, message) {\n    super(message);\n    // set name and field here\n  }\n}\n\n// 2. Return whole cents, or throw a ValidationError for the \"amount\" field\nfunction parseAmount(text) {\n  return Math.round(Number(text) * 100);\n}\n\n// 3. Return { ok: true, list } with the new expense added, or\n//    { ok: false, field, message } for a ValidationError.\n//    Any other error must NOT be swallowed.\nfunction tryAdd(list, label, amountText) {\n  try {\n    const expense = { label: label.trim(), amount: parseAmount(amountText) };\n    return { ok: true, list: [...list, expense] };\n  } catch (error) {\n    return { ok: false };\n  }\n}\n\nconsole.log(tryAdd([], \"Tea\", \"abc\"));\n"},"solution":{"js":"class ValidationError extends Error {\n  constructor(field, message) {\n    super(message);\n    this.name = \"ValidationError\";\n    this.field = field;\n  }\n}\n\nfunction parseAmount(text) {\n  const dollars = Number(text);\n  if (text.trim() === \"\" || !Number.isFinite(dollars) || dollars <= 0) {\n    throw new ValidationError(\"amount\", \"Enter an amount greater than zero.\");\n  }\n  return Math.round(dollars * 100);\n}\n\nfunction tryAdd(list, label, amountText) {\n  try {\n    if (label.trim() === \"\") {\n      throw new ValidationError(\"label\", \"Enter what you spent money on.\");\n    }\n    const expense = { label: label.trim(), amount: parseAmount(amountText) };\n    return { ok: true, list: [...list, expense] };\n  } catch (error) {\n    if (error instanceof ValidationError) {\n      return { ok: false, field: error.field, message: error.message };\n    }\n    throw error;\n  }\n}\n\nconsole.log(tryAdd([], \"Tea\", \"abc\"));\n"},"checks":[{"id":"error-class","test":"const e = new ValidationError('amount', 'Bad');\nreturn e instanceof Error && e.name === 'ValidationError' && e.field === 'amount' && e.message === 'Bad';\n"},{"id":"parse-ok","test":"return parseAmount('4.10') === 410 && parseAmount(' 12 ') === 1200;\n"},{"id":"parse-throws","test":"const bad = ['', '   ', 'abc', '0', '-5', 'Infinity'];\nreturn bad.every((t) => { try { parseAmount(t); return false; } catch (e) { return e instanceof ValidationError && e.field === 'amount'; } });\n"},{"id":"try-add-ok","test":"const r = tryAdd([{ label: 'Bus', amount: 270 }], '  Tea ', '3');\nreturn r.ok === true && r.list.length === 2 && r.list[1].label === 'Tea' && r.list[1].amount === 300;\n"},{"id":"try-add-invalid","test":"const a = tryAdd([], 'Tea', 'abc');\nconst b = tryAdd([], '  ', '3');\nreturn a.ok === false && a.field === 'amount' && typeof a.message === 'string' && a.message.length > 0 && b.ok === false && b.field === 'label';\n"},{"id":"rethrows","test":"try { tryAdd(null, 'Tea', '3'); return false; } catch (e) { return e instanceof TypeError; }\n"}],"title":"ارمِ ما يستطيع المستخدمون إصلاحه، وأعِد رمي الباقي","prompt":"<ol>\n<li>أكمل <code dir=\"ltr\">ValidationError</code> بحيث تكون قيمة <code dir=\"ltr\">name</code> في الـ instances هي <code dir=\"ltr\">&quot;ValidationError&quot;</code>، وتكون لها خاصية <code dir=\"ltr\">field</code>.</li>\n<li>اجعل <code dir=\"ltr\">parseAmount(text)</code> تُرجع سنتات صحيحة، أو ترمي <code dir=\"ltr\">ValidationError</code> بالحقل <code dir=\"ltr\">&quot;amount&quot;</code> حين يكون النص فارغًا، أو ليس رقمًا منتهيًا، أو ليس أكبر من 0.</li>\n<li>أصلح <code dir=\"ltr\">tryAdd(list, label, amountText)</code>. مع الإدخال الصالح تُرجع <code dir=\"ltr\">{ ok: true, list }</code> وقد أُضيف إليها المصروف الجديد (بتسمية مشذّبة، والمبلغ بالسنتات). والتسمية الفارغة ترمي <code dir=\"ltr\">ValidationError</code> بالحقل <code dir=\"ltr\">&quot;label&quot;</code>. وأخطاء التحقّق تتحوّل إلى <code dir=\"ltr\">{ ok: false, field, message }</code>. <strong>وأي خطأ آخر يجب أن يُعاد رميه</strong>، لا أن يُبتلع.</li>\n</ol>\n","hints":["في الـ constructor، بعد <code dir=\"ltr\">super(message)</code>: <code dir=\"ltr\">this.name = &quot;ValidationError&quot;; this.field = field;</code>.","<code dir=\"ltr\">Number(&quot;&quot;)</code> تساوي 0 و<code dir=\"ltr\">Number(&quot;Infinity&quot;)</code> تساوي Infinity، لذلك افحص <code dir=\"ltr\">text.trim() === &quot;&quot;</code> و<code dir=\"ltr\">Number.isFinite(dollars) &amp;&amp; dollars &gt; 0</code>.","في الـ <code dir=\"ltr\">catch</code>: <code dir=\"ltr\">if (error instanceof ValidationError) { return { ok: false, field: error.field, message: error.message }; } throw error;</code>."],"explanation":"<p>يرث <code dir=\"ltr\">ValidationError</code> من <code dir=\"ltr\">Error</code>، فيحتفظ برسالة وأثر مكدّس، ويضيف الحقل <code dir=\"ltr\">field</code> الذي يحتاجه النموذج لوضع الرسالة. وترمي <code dir=\"ltr\">parseAmount</code> خطأً بدلًا من إرجاع قيمة خاصة، فلا يستطيع المُستدعون معاملة مبلغ غير صالح على أنه رقم عن طريق الخطأ. وفي <code dir=\"ltr\">tryAdd</code>، تفحص <code dir=\"ltr\">catch</code> الشرط <code dir=\"ltr\">instanceof ValidationError</code> ولا تحوّل إلى نتيجة ودودة إلا هذه الأخطاء؛ أما أي شيء آخر، مثل الـ <code dir=\"ltr\">TypeError</code> الناتج عن نشر <code dir=\"ltr\">null</code>، فهو خطأ برمجي في الكود المُستدعي، فيُعاد رميه كي يظهر في الـ console بدلًا من أن يختفي.</p>\n","checkLabels":{"error-class":"<code dir=\"ltr\">ValidationError</code> هو Error له name وfield","parse-ok":"<code dir=\"ltr\">parseAmount</code> تُرجع سنتات صحيحة مع النص الصالح","parse-throws":"<code dir=\"ltr\">parseAmount</code> ترمي ValidationError مع المدخلات الفارغة أو غير الرقمية أو الصفرية أو السالبة أو اللانهائية","try-add-ok":"<code dir=\"ltr\">tryAdd</code> تضيف المصروف الصالح","try-add-invalid":"<code dir=\"ltr\">tryAdd</code> تُرجع { ok: false, field, message } مع المدخلات غير الصالحة","rethrows":"<code dir=\"ltr\">tryAdd</code> تعيد رمي الأخطاء التي ليست من نوع ValidationError"}},"words":664,"readMinutes":4}