كيفية التعامل مع عمليات الاسترجاعات المتداخلة وتجنب "معاودة الاتصال بالجحيم"

الصورة من قبل جيفرسون سانتوس على Unsplash

جافا سكريبت هي لغة غريبة. ذات مرة ، يجب أن تتعامل مع رد اتصال موجود في رد اتصال آخر يكون في رد اتصال آخر.

الناس يسمون بمودة هذا النمط الجحيم رد الاتصال.

يبدو كيندا مثل هذا:

firstFunction (args، function () {
  secondFunction (args، function () {
    thirdFunction (args، function () {
      // وهلم جرا…
    })؛
  })؛
})؛

هذا هو جافا سكريبت بالنسبة لك. من المحير أن نرى عمليات رد متداخلة ، لكنني لا أعتقد أنها "جحيم". يمكن التحكم في "الجحيم" إذا كنت تعرف ما يجب القيام به.

على الاسترجاعات

أفترض أنك تعرف ماهية عمليات الاسترجاعات إذا كنت تقرأ هذا المقال. إذا لم تقم بذلك ، فالرجاء قراءة هذه المقالة للحصول على مقدمة لعمليات الاسترجاعات قبل المتابعة. هناك ، نتحدث عن ماهية عمليات الاسترجاعات ولماذا تستخدمها في JavaScript.

حلول لرد الجحيم

هناك أربعة حلول لإعادة الاتصال بالجحيم:

  1. اكتب التعليقات
  2. انقسام الوظائف إلى وظائف أصغر
  3. باستخدام الوعود
  4. باستخدام Async / انتظار

قبل أن نتعمق في الحلول ، دعونا نبني جحيم رد اتصال معًا. لماذا ا؟ نظرًا لأنه من المستخلص أن ترى الدالة الأولى ، الوظيفة الثانية ، الوظيفة الثالثة. نريد أن نجعلها ملموسة.

بناء الجحيم رد الاتصال

دعونا نتخيل أننا نحاول صنع برغر. لعمل برغر ، نحتاج إلى متابعة الخطوات التالية:

  1. احصل على المكونات (سنفترض أنها برغر لحم البقر)
  2. طبخ اللحم البقري
  3. الحصول على الكعك برغر
  4. ضع اللحم البقري المطبوخ بين الكعك
  5. خدمة برغر

إذا كانت هذه الخطوات متزامنة ، فستنظر إلى وظيفة تشبه هذه:

const makeBurger = () => {
  const beef = getBeef () ؛
  const patty = cookBeef (beef)؛
  const الكعك = getBuns () ؛
  const burger = putBeefBetweenBuns (الكعك ، لحم البقر) ؛
  برغر العودة ؛
}؛
const burger = makeBurger ()؛
خدمة (برغر)؛

ومع ذلك ، في السيناريو الخاص بنا ، دعنا نقول أننا لا نستطيع جعل البرجر أنفسنا. علينا أن نعلم المساعد على الخطوات اللازمة لجعل برغر. بعد أن نعلم المساعد ، يتعين علينا الانتظار حتى ينتهي المساعد قبل أن نبدأ الخطوة التالية.

إذا كنا نريد انتظار شيء في JavaScript ، نحتاج إلى استخدام رد اتصال. لجعل البرغر ، علينا أن نحصل على اللحم البقري أولاً. يمكننا فقط طهي اللحم البقري بعد أن نحصل على اللحم البقري.

const makeBurger = () => {
  getBeef (دالة (لحم بقر) {
    / / يمكننا فقط طهي اللحم البقري بعد أن نحصل عليه.
  })؛
}؛

لطهي اللحم البقري ، نحتاج إلى تمرير اللحم البقري إلى وظيفة cookBeef. خلاف ذلك ، لا يوجد شيء للطهي! ثم ، علينا أن ننتظر حتى يتم طهي اللحم البقري.

بمجرد طهي اللحم البقري ، نحصل على الكعك.

const makeBurger = () => {
  getBeef (دالة (لحم بقر) {
    cookBeef (لحوم البقر ، وظيفة (cookedBeef) {
      getBuns (دالة (الكعك) {
        // ضع الفطيرة في الكعكة
      })؛
    })؛
  })؛
}؛

بعد أن نحصل على الكعك ، نحتاج إلى وضع الفطيرة بين الكعك. هذا هو المكان الذي يتم تشكيل برغر.

const makeBurger = () => {
  getBeef (دالة (لحم بقر) {
    cookBeef (لحوم البقر ، وظيفة (cookedBeef) {
      getBuns (دالة (الكعك) {
        putBeefBetweenBuns (الكعك ، ولحم البقر ، وظيفة (برغر) {
            / خدمة البرغر
        })؛
      })؛
    })؛
  })؛
}؛

أخيرًا ، يمكننا تقديم البرجر! لكن لا يمكننا إرجاع برغر من makeBurger لأنه غير متزامن. نحن بحاجة إلى قبول رد اتصال لخدمة البرغر.

const makeBurger = nextStep => {
  getBeef (دالة (لحم بقر) {
    cookBeef (لحوم البقر ، وظيفة (cookedBeef) {
      getBuns (دالة (الكعك) {
        putBeefBetweenBuns (الكعك ، ولحم البقر ، وظيفة (برغر) {
          صممته (برغر)
        })
      })
    })
  })
}
// اصنع وخدم البرغر
makeBurger (function (burger) => {
  خدمة (برغر)
})

(لقد كان من دواعي سروري جعل هذا مثال الجحيم رد الاتصال ).

الحل الأول لالجحيم رد الاتصال: كتابة التعليقات

makeBurger رد الاتصال الجحيم بسيط لفهم. يمكننا قراءتها. انها مجرد ... لا تبدو لطيفة.

إذا كنت تقرأ makeBurger للمرة الأولى ، فقد تعتقد "لماذا بحق الجحيم نحتاج إلى العديد من عمليات الاسترجاعات لعمل برغر؟ هذا ليس منطقيا! ".

في مثل هذه الحالة ، تريد ترك تعليقات لشرح الشفرة الخاصة بك.

// يجعل برغر
// makeBurger يحتوي على أربع خطوات:
// 1. الحصول على لحوم البقر
// 2. طهي اللحم البقري
// 3. الحصول على الكعك لبرغر
// 4. ضع اللحم البقري المطبوخ بين الكعك
// 5. خدمة البرغر (من رد الاتصال)
/ / نستخدم عمليات الاستدعاء هنا لأن كل خطوة غير متزامنة.
// علينا أن ننتظر المساعد لإكمال الخطوة الواحدة
// قبل أن نبدأ الخطوة التالية
const makeBurger = nextStep => {
  getBeef (دالة (لحم بقر) {
    cookBeef (لحوم البقر ، وظيفة (cookedBeef) {
      getBuns (دالة (الكعك) {
        putBeefBetweenBuns (الكعك ، ولحم البقر ، وظيفة (برغر) {
          صممته (برغر)؛
        })؛
      })؛
    })؛
  })؛
}؛

الآن ، بدلاً من التفكير في "wtf ؟!" عندما ترى جحيم رد الاتصال ، تفهم سبب وجوب كتابتها بهذه الطريقة.

الحل الثاني لجحيم معاودة الاتصال: قم بتقسيم عمليات معاودة الاتصال إلى وظائف مختلفة

مثال الجحيم رد الاتصال لدينا هو بالفعل مثال على ذلك. اسمح لي أن أريك الشفرة الإلزامية خطوة بخطوة وسترى السبب.

بالنسبة إلى getBeef ، رد الاتصال الأول لدينا ، يتعين علينا الذهاب إلى الثلاجة للحصول على اللحم البقري. هناك نوعان من الثلاجات في المطبخ. نحن بحاجة للذهاب إلى الثلاجة الصحيحة.

const getBeef = nextStep => {
  const الثلاجة = leftFright.
  لحوم البقر const = getBeefFromFridge (الثلاجة) ؛
  صممته (لحوم البقر)؛
}؛

لطهي اللحم البقري ، نحتاج إلى وضع اللحم البقري في الفرن ؛ اقلب الفرن إلى 200 درجة ، وانتظر لمدة عشرين دقيقة.

const cookBeef = (beef، nextStep) => {
  const workInProgress = putBeefinOven (beef)؛
  setTimeout (function () {
    صممته (workInProgress)؛
  } ، 1000 * 60 * 20) ؛
}؛

الآن تخيل لو كان عليك أن تكتب كل خطوة من هذه الخطوات في makeBurger ... ربما تكون خافتًا من مقدار الشفرة المطلق!

للحصول على مثال ملموس حول تقسيم عمليات الاسترجاعات إلى وظائف أصغر ، يمكنك قراءة هذا القسم الصغير في مقالة رد الاتصال الخاصة بي.

الحل الثالث لجحيم معاودة الاتصال: استخدم الوعود

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

يمكن أن تجعل الوعود جحيم رد الاتصال أسهل بكثير. بدلاً من الرمز المتداخل الذي تراه أعلاه ، سيكون لديك هذا:

const makeBurger = () => {
  إرجاع getBeef ()
    .ثم (beef => cookBeef (beef))
    .then (cookedBeef => getBuns (beef))
    .then (bunsAndBeef => putBeefBetweenBuns (bunsAndBeef)) ؛
}؛
// اصنع وخدم برجر
makeBurger (). ثم (burger => serve (burger)) ؛

إذا كنت تستفيد من أسلوب الحجة الفردية بالوعود ، فيمكنك تعديل ما سبق إلى ما يلي:

const makeBurger = () => {
  إرجاع getBeef ()
    . ثم (cookBeef)
    . ثم (getBuns)
    . ثم (putBeefBetweenBuns)؛
}؛
// اصنع وخدم برجر
makeBurger () ثم (خدمة)؛

أسهل بكثير في القراءة والإدارة.

ولكن السؤال هو كيف يمكنك تحويل التعليمات البرمجية المستندة إلى رد الاتصال إلى رمز يستند إلى وعد.

تحويل عمليات الاسترجاعات إلى الوعود

لتحويل عمليات الاسترجاعات إلى وعود ، نحتاج إلى إنشاء وعد جديد لكل رد اتصال. يمكننا حل الوعد عندما يكون رد الاتصال ناجحًا. أو يمكننا رفض الوعد إذا فشل رد الاتصال.

const getBeefPromise = _ => {
  const الثلاجة = leftFright.
  لحوم البقر const = getBeefFromFridge (الثلاجة) ؛
  إرجاع وعد جديد ((حل ، رفض) => {
    إذا (لحم البقر) {
      حل (لحوم البقر)؛
    } آخر {
      رفض (خطأ جديد ("لا مزيد من لحوم البقر!")) ؛
    }
  })؛
}؛
const cookBeefPromise = beef => {
  const workInProgress = putBeefinOven (beef)؛
  إرجاع وعد جديد ((حل ، رفض) => {
    setTimeout (function () {
      تصميم (workInProgress)؛
    } ، 1000 * 60 * 20) ؛
  })؛
}؛

في الممارسة العملية ، من المحتمل أن تتم كتابة الاستدعاءات لك بالفعل. إذا كنت تستخدم Node ، فستكون لكل وظيفة تحتوي على رد اتصال نفس بناء الجملة:

  1. سيكون رد الاتصال الوسيطة الأخيرة
  2. سيكون رد الاتصال دائمًا وسيطين. وهذه الحجج هي في نفس الترتيب. (خطأ أولاً ، متبوعًا بكل ما تهتم به).
// الوظيفة المحددة لك
const functionName = (arg1، arg2، callback) => {
  // افعل الأشياء هنا
  رد الاتصال (يخطئ ، الاشياء) ؛
}؛
// كيف تستخدم الوظيفة
functionName (arg1 ، arg2 ، (err ، stuff) => {
  إذا (يخطئ) {
  console.error (يخطئ)؛
  }
  // افعل اشياء
})؛

إذا كان رد الاتصال الخاص بك يحتوي على نفس بناء الجملة ، فيمكنك استخدام مكتبات مثل ES6 Promisify أو Denodeify (إلغاء تحديد العقدة - ify) التي تتحول إلى رد اتصال. إذا كنت تستخدم Node v8.0 فما فوق ، يمكنك استخدام util.promisify.

كل ثلاثة منهم يعملون. يمكنك اختيار أي مكتبة للعمل معها. هناك فروق دقيقة بسيطة بين كل طريقة. سأتركك للتحقق من وثائقهم لمعرفة كيفية القيام بذلك.

الحل الرابع لجحيم معاودة الاتصال: استخدم وظائف غير متزامنة

لاستخدام وظائف غير متزامنة ، تحتاج إلى معرفة شيئين أولاً:

  1. كيفية تحويل عمليات الاسترجاعات إلى وعود (اقرأ أعلاه)
  2. كيفية استخدام وظائف غير متزامنة (اقرأ هذا إذا كنت بحاجة إلى مساعدة).

مع وظائف غير متزامنة ، يمكنك كتابة makeBurger كما لو كانت متزامنة مرة أخرى!

const makeBurger = async () => {
  const beef = await getBeef ()؛
  const cookedBeef = في انتظار cookBeef (لحوم البقر) ؛
  const الكعك = انتظار getBuns () ؛
  const burger = في انتظار putBeefBetweenBuns (cookedBeef ، الكعك) ؛
  برغر العودة ؛
}؛
// اصنع وخدم برجر
makeBurger () ثم (خدمة)؛

هناك تحسين واحد يمكننا إجراؤه على makeBurger هنا. ربما يمكنك الحصول على اثنين من المساعدين لـ getBuns و getBeef في نفس الوقت. هذا يعني أنه يمكنك انتظارهما مع Promise.all.

const makeBurger = async () => {
  const [beef، buns] = في انتظار Promise.all (getBeef، getBuns)؛
  const cookedBeef = في انتظار cookBeef (لحوم البقر) ؛
  const burger = في انتظار putBeefBetweenBuns (cookedBeef ، الكعك) ؛
  برغر العودة ؛
}؛
// اصنع وخدم برجر
makeBurger () ثم (خدمة)؛

(ملاحظة: يمكنك أن تفعل الشيء نفسه مع Promises ... لكن بناء الجملة ليس لطيفًا وواضحًا مثل وظائف المزامنة / الانتظار).

تغليف

رد الاتصال بحق الجحيم ليس جهنمًا كما تعتقد. هناك أربع طرق سهلة لإدارة الجحيم رد الاتصال:

  1. اكتب التعليقات
  2. انقسام الوظائف إلى وظائف أصغر
  3. باستخدام الوعود
  4. باستخدام Async / انتظار

هذه المقالة نشرت أصلا على مدونتي.
اشترك في النشرة الإخبارية إذا كنت تريد المزيد من المقالات لمساعدتك في أن تصبح مطور واجهة أفضل.