اذهب إلى المحتوى

الدوال الواعدة: تحويل الدوال إلى وعود Promisification في جافاسكربت


صفا الفليج

تحويل الدوال إلى وعود (Promisification) هي عملية تغليف الدالة التي تستلم ردّ نداء لتصبح دالة تُعيد وعدًا.

وفي الحياة العملية فهذا النوع من التحويل مطلوب جدًا إذ تعتمد العديد من الدوال والمكتبات على ردود النداء. ولكن… الوعود أسهل وأفضل لذا من المنطقي تحويل تلك الدوال.

لنأخذ مثلًا دالة loadScript(src, callback)‎ من الفصل مقدمة إلى ردود النداء callback:

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Script load error for ${src}`));

  document.head.append(script);
}

// الاستعمال
// loadScript('path/script.js', (err, script) => {...})

هيا نحوّلها. على الدالة الجديدة loadScriptPromise(src)‎ القيام بنفس ما تقوم به تلك، ولكن لا تقبل إلّا src وسيطًا (بدون callback) وتُعيد وعدًا.

let loadScriptPromise = function(src) {
  return new Promise((resolve, reject) => {
    loadScript(src, (err, script) => {
      if (err) reject(err)
      else resolve(script);
    });
  })
}

// الاستعمال
// loadScriptPromise('path/script.js').then(...)

الآن يمكننا استعمال loadScriptPromise بسهولة بالغة في الشيفرات التي تعتمد الوعود.

وكما نرى فالدالة تكلّف الدالة الأصلية loadScript بكلّ العمل اللازم، وما تفعله هو تقديم ردّ نداء من عندها تحوّله إلى وعد resolve/reject.

نحتاج عمليًا إلى تحويل دوال عديدة وكثيرة لتعتمد الوعود، لذا من المنطقي استعمال دالة مساعِدة.

لنسمّها promisify(f)‎ وستقبل دالة الأصل f اللازم تحويلها، وتُعيد دالة غالِفة.

يؤدّي الغلاف نفس عمل الشيفرة أعلاه: يُعيد وعدًا ويمرّر النداء إلى الدالة الأصلية f متتّبعًا الناتج في ردّ نداء يصنعه بنفسه:

function promisify(f) {
  return function (...args) { // يعيد التابع المغلّف
    return new Promise((resolve, reject) => {
      function callback(err, result) { // ‫رد النداء خاصتنا لـِ f
        if (err) {
          return reject(err);
        } else {
          resolve(result);
        }
      }

      args.push(callback); // ‫نضيف رد النداء خاصتنا إلى نهاية واسطاء f

      f.call(this, ...args); // استدعي التابع الأصلي
    });
  };
};

// ‫طريقة الاستخدام:
let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);

نفترض هنا بأنّ الدالة الأصلية تتوقّع استلام ردّ نداء له وسيطين (err, result)، وهذا ما نواجهه أغلب الوقت لهذا كتبنا ردّ النداء المخصّص بهذا التنسيق، وتعمل دالة promisify على أكمل وجه… لهذه الحالات.

ولكن ماذا لو كانت تتوقّع f ردّ نداء له وسطاء أكثر callback(err, res1, res2, ...)‎؟

إليك نسخة promisify ذكية محسّنة: لو استدعيناها هكذا promisify(f, true)‎ فسيكون ناتج الوعد مصفوفة من نواتج ردود النداء [res1, res2,‎ ...‎]:

// ‫التابع promisify(f, true)‎ لجب مصفوفة النتائج
function promisify(f, manyArgs = false) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      function callback(err, ...results) { // ‫رد النداء خاصتنا لـِ f
        if (err) {
          return reject(err);
        } else {
          // إجلب جميع ردود النداء وإذا حدّد أكثر من وسيط 
          resolve(manyArgs ? results : results[0]);
        }
      }

      args.push(callback);

      f.call(this, ...args);
    });
  };
};

// ‫طريقة الاستخدام:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...)

أمّا لتنسيقات ردود النداء الشاذّة (مثل التي بدون وسيط err أصلًا callback(result)‎) فيمكننا تحويلها يدويًا بدون استعمال الدالة المساعِدة.

كما وهناك وحدات لها دوال تحويل مرنة أكثر في التعامل مثل es6-promisify. وفي Node.js نرى الدالة المضمّنة util.promisify لهذا الغرض.

ملاحظة: يعدّ تحويل الدوال إلى وعود نهجًا رائعًا، خاصةً عند استخدام async/await (الّتي سنراها في الفصل التالي)، ولكن ليس بديلًا كليًا لردود النداء.

تذكر أن الوعد له نتيجة واحدة فقط، ولكن تقنيًا ممكن أن تُستدعى ردود النداء عدة مرات.

لذا فإن تحويل الدوال إلى وعود مخصصة للدوال التي تستدعي ردود النداء لمرة واحدة. وستُتجاهل جميع الاستدعاءات اللاحقة.

ترجمة -وبتصرف- للفصل Promisification من كتاب The JavaScript language


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...