«أقوم بتشغيل دالة، لكنها ترجع undefined!» — ألم مألوف؟
\nتخيل: أنت تكتب كوداً بلغة JavaScript، تقوم بطلب إلى API، تنتظر البيانات… وتحصل في المقابل على undefined. أو الأسوأ — Promise {<pending>}. تستسلم، وتريد إغلاق المتصفح والذهاب للاعتكاف.
لا تتعجل. هذه المشكلة تؤرق حتى المطورين ذوي الخبرة. ليس من قبيل الصدفة أن سؤال «How do I return the response from an asynchronous call?» على Stack Overflow حصد آلاف المشاهدات ومئات الإجابات. اليوم سنحل هذه المشكلة نهائياً.
\nفي هذه المقالة ستتعلم:
\n- \n
- لماذا لا يعمل
returnبالطريقة التي اعتدت عليها \n - كيف تروض عدم التزامن باستخدام دوال الاسترجاع، الوعود، و
async/await\n - أي نهج تختار لمشروعك \n
هيا بنا!
\n\nلماذا لا يعيد return البيانات؟
\nدعنا نذهب مباشرة إلى صلب الموضوع. إليك مثالاً كلاسيكياً:
\n\nfunction getUserData(userId) {\n fetch(`https://api.example.com/users/${userId}`)\n .then(response => response.json())\n .then(data => {\n return data; // يبدو أن كل شيء على ما يرام؟\n });\n}\n\nconst user = getUserData(42);\nconsole.log(user); // undefined\n\nمألوف؟ لقد كتبت return data داخل .then()، لكنك حصلت على undefined في الخارج. لماذا؟
JavaScript — أحادي الخيط وغير مانع. عندما تستدعي fetch، فإنه لا ينتظر رد الخادم. بدلاً من ذلك، «يؤجل» تنفيذ دالة الاسترجاع جانباً وينتقل فوراً إلى السطر التالي. والسطر التالي هو console.log(user)، حيث user لم يُحدد بعد.
دالتك getUserData تنتهي قبل أن يصل رد الخادم. إنها لا تُرجع شيئاً (تُرجع undefined)، والبيانات «معلقة» في الهواء.
كيف نصلح هذا؟ هناك ثلاث طرق رئيسية.
\n\nالطريقة 1: دوال الاسترجاع (Callbacks) — جد عدم التزامن
\nسابقاً، قبل ظهور الوعود، كانت جميع العمليات غير المتزامنة تتم عبر دوال الاسترجاع. تقوم بتمرير دالة سيتم استدعاؤها عند وصول البيانات.
\n\nfunction getUserData(userId, callback) {\n fetch(`https://api.example.com/users/${userId}`)\n .then(response => response.json())\n .then(data => {\n callback(data); // نستدعي دالة الاسترجاع مع البيانات\n })\n .catch(error => {\n callback(null, error); // أو نمرر الخطأ\n });\n}\n\n// الاستخدام:\ngetUserData(42, (data, error) => {\n if (error) {\n console.error('خطأ:', error);\n return;\n }\n console.log('بيانات المستخدم:', data);\n});\n\nالإيجابيات: البساطة، تعمل في كل مكان.
\nالسلبيات: إذا قمت بتداخل عدة دوال استرجاع داخل بعضها، ستحصل على «جحيم دوال الاسترجاع» (callback hell). يصبح الكود غير قابل للقراءة.
\n\ngetUserData(42, (data1) => {\n getPosts(data1.id, (data2) => {\n getComments(data2[0].id, (data3) => {\n // ... وهكذا دواليك\n });\n });\n});\n\nJavaScript الحديثة تخلت عن هذا. لكن فهم دوال الاسترجاع ضروري — فهي أساس جميع النهج الأخرى.
\n\nالطريقة 2: الوعود (Promises) — منقذو قابلية القراءة
\nالوعد هو كائن يمثل «تعهداً» بإرجاع قيمة في المستقبل. له ثلاث حالات: pending (انتظار)، fulfilled (تم التنفيذ)، و rejected (مرفوض).
لنعد إلى مثالنا. بدلاً من دالة الاسترجاع، نعيد الوعد نفسه:
\n\nfunction getUserData(userId) {\n return fetch(`https://api.example.com/users/${userId}`)\n .then(response => {\n if (!response.ok) {\n throw new Error('الشبكة ليست في حالة جيدة');\n }\n return response.json();\n });\n}\n\n// الاستخدام:\nconst userPromise = getUserData(42);\n\nuserPromise\n .then(data => {\n console.log('بيانات المستخدم:', data);\n })\n