كيفية عمل وعد من وظيفة رد الاتصال في JavaScript

يواجه مطورو الواجهة الخلفية تحديات طوال الوقت أثناء إنشاء التطبيقات أو اختبار التعليمات البرمجية. بصفتي مطورًا جديدًا إلى حد ما ويتعرف على تلك التحديات ، لم أواجه تحديًا أو إزعاجًا بشكل متكرر - أو لا يُنسى - أكثر من وظائف رد الاتصال.

لن أتعمق كثيرًا في تفاصيل رد الاتصال وإيجابياته وسلبياته أو بدائل مثل الوعود وعدم التزامن / الانتظار. للحصول على شرح أكثر وضوحا ، يمكنك مراجعة هذه المقالة التي تشرحها بدقة.

رد الجحيم

عمليات الاسترجاعات هي ميزة مفيدة لجافا سكريبت تمكنها من إجراء مكالمات غير متزامنة. إنها وظائف يتم تمريرها عادةً كمعامل ثانٍ إلى وظيفة أخرى تقوم بإحضار البيانات أو إجراء عملية إدخال / إخراج تستغرق وقتًا لإكمالها.

على سبيل المثال ، حاول إجراء مكالمة API باستخدام امتداد requestالوحدة النمطية أو الاتصال بقاعدة بيانات MongoDB. ولكن ماذا لو كان كلا النداءين يعتمدان على بعضهما البعض؟ ماذا لو كانت البيانات التي تجلبها هي عنوان MongoDB URL الذي تحتاج إلى الاتصال به؟

يجب أن تتداخل هذه المكالمات داخل بعضها البعض:

request.get(url, function(error, response, mongoUrl) { if(error) throw new Error("Error while fetching fetching data"); MongoClient.connect(mongoUrl, function(error, client) { if(error) throw new Error("MongoDB connection error"); console.log("Connected successfully to server"); const db = client.db("dbName"); // Do some application logic client.close(); }); });

حسنًا ... أين المشكلة؟ حسنًا ، من ناحية ، فإن قابلية قراءة الشفرة تعاني من هذه التقنية.

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

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

أسمع بعضًا منكم ، بما في ذلك ذاتي الساذجة ، يخبرونني أن ألفها في asyncوظيفة ثم await وظيفة رد الاتصال. هذا فقط لا يعمل.

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

هذا هو الخطأ الذي فعلته من قبل:

var request = require('request'); // WRONG async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" await request.get(url, function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined }; // Wrong async function(){ let joke; let url = "//api.chucknorris.io/jokes/random" request.get(url, await function(error, response, data) { if(error) throw new Error("Error while fetching fetching data"); let content = JSON.parse(data); joke = content.value; }); console.log(joke); // undefined };

قد يقول بعض المطورين الأكثر خبرة "ما عليك سوى استخدام مكتبة مختلفة تستخدم الوعود لفعل الشيء نفسه ، مثل axios ،أو مجرد استخدام الجلب " . بالتأكيد أستطيع في هذا السيناريو ، لكن هذا مجرد هروب من المشكلة.

الى جانب ذلك ، هذا مجرد مثال. في بعض الأحيان يمكن أن تكون مقيدًا باستخدام مكتبة لا تدعم الوعود بدون بدائل. مثل استخدام مجموعات تطوير البرامج (SDKs) للتواصل مع منصات مثل Amazon Web Services (AWS) أو Twitter أو Facebook.

في بعض الأحيان ، حتى استخدام رد اتصال لإجراء مكالمة بسيطة للغاية مع عملية I / O أو CRUD سريعة أمر جيد ، ولا يعتمد أي منطق آخر على نتائجه. ولكن قد تكون مقيدًا ببيئة وقت التشغيل كما هو الحال في وظيفة Lambda والتي قد تقضي على كل العمليات بمجرد انتهاء مؤشر الترابط الرئيسي ، بغض النظر عن أي مكالمات غير متزامنة لم تكتمل.

الحل 1 (سهل): استخدم الوحدة النمطية "المنفعة" الخاصة بالعقدة

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

كما أوضح Erop و Robin في التعليقات ، يدعم الإصدار 8 من Nodejs الآن تحويل وظائف رد الاتصال إلى وعود باستخدام وحدة الاستخدام المدمجة .

const request = require('request'); const util = require('util'); const url = "//api.chucknorris.io/jokes/random"; // Use the util to promisify the request method const getChuckNorrisFact = util.promisify(request); // Use the new method to call the API in a modern then/catch pattern getChuckNorrisFact(url).then(data => { let content = JSON.parse(data.body); console.log('Joke: ', content.value); }).catch(err => console.log('error: ', err))

الكود أعلاه يحل المشكلة بدقة باستخدام use.promisifyالطريقة المتاحة من مكتبة nodejs الأساسية.

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

ثم يمكنك استخدام هذا المتغير بوصفها وظيفة التي يمكنك استخدامها مثل الوعد مع . ثم () و ) (.catch الأساليب.

الحل 2 (متضمن): تحويل رد الاتصال إلى وعد

في بعض الأحيان ، لا يكون استخدام مكتبات الطلب والاستخدام ممكنًا ، سواء كان ذلك بسبب بيئة / قاعدة رمز قديمة أو تنفيذ الطلبات من المتصفح من جانب العميل ، يجب عليك الوفاء بوعد حول وظيفة رد الاتصال الخاصة بك.

لنأخذ مثال تشاك نوريس أعلاه ، ونحول ذلك إلى وعد.

var request = require('request'); let url = "//api.chucknorris.io/jokes/random"; // A function that returns a promise to resolve into the data //fetched from the API or an error let getChuckNorrisFact = (url) => { return new Promise( (resolve, reject) => { request.get(url, function(error, response, data){ if (error) reject(error); let content = JSON.parse(data); let fact = content.value; resolve(fact); }) } ); }; getChuckNorrisFact(url).then( fact => console.log(fact) // actually outputs a string ).catch( error => console.(error) );

في الكود أعلاه ، وضعت requestالوظيفة القائمة على رد الاتصال داخل غلاف الوعد Promise( (resolve, reject) => { //callback function}). يسمح لنا هذا الغلاف باستدعاء getChuckNorrisFactالوظيفة مثل الوعد بالطرق .then()و .catch(). عندما getChuckNorrisFactيتم استدعاء الأمر ، فإنه ينفذ الطلب إلى واجهة برمجة التطبيقات وينتظر تنفيذ أي resolve()أو reject()بيان. في وظيفة رد الاتصال ، يمكنك ببساطة تمرير البيانات المستردة إلى طرق الحل أو الرفض.

بمجرد أن يتم جلب البيانات (في هذه الحالة ، حقيقة رائعة لـ Chuck Norris) وتمريرها إلى المحلل ، يتم getChuckNorrisFactتنفيذ then()الطريقة. سيؤدي هذا إلى إرجاع النتيجة التي يمكنك استخدامها داخل دالة داخلthen() لتنفيذ المنطق المطلوب - في هذه الحالة عرضها على وحدة التحكم.

يمكنك قراءة المزيد عنها في MDN Web Docs.