فهم JavaScript غير المتزامن: Callbacks ، و Promises ، و Async / Await

جافا سكريبت ، شبيبة ، شعار

ما هي البرمجة المتزامنة؟

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

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

كيفية إنشاء برمجة مزامنة في JavaScript

فيما يلي مثال بسيط على البرمجة المتزامنة في JavaScript:

function task1() {
  console.log('Task 1');
}

function task2() {
  console.log('Task 2');
}

function task3() {
  console.log('Task 3');
}

task1();
task2();
task3();

في هذا المثال ، يتم تنفيذ الوظائف المهمة 1 ، والمهمة 2 ، والمهمة 3 بالترتيب الذي يطلق عليها. سيكون الإخراج:

Task 1
Task 2
Task 3

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

ما هي البرمجة غير المتزامنة؟

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

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

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

كيفية إنشاء برمجة غير متزامنة في JavaScript

فيما يلي مثال بسيط على البرمجة غير المتزامنة في JavaScript باستخدام setTimeout :

function task1() {
  console.log('Task 1');
}

function task2() {
  console.log('Task 2');
}

function task3() {
  console.log('Task 3');
}

task1();
setTimeout(task2, 1000); // Execute task2 after a 1000ms delay
task3();

في هذا المثال ، المهمة 1 والمهمة 3 بشكل متزامن ، بينما المهمة 2 بشكل غير متزامن بعد تأخير قدره 1000 ميلي ثانية. سيكون الإخراج:

Task 1
Task 3
Task 2

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

كيف تعمل الاسترجاعات في JavaScript

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

عمليات الاسترجاعات هي طريقة لإدارة تدفق البرنامج عند التعامل مع الأحداث غير المتزامنة. بتمرير دالة كوسيطة ، يمكنك التأكد من أنه لا يتم استدعاء الوظيفة إلا عند انتهاء العملية غير المتزامنة.

قم بإنشاء مثال باستخدام وظيفة رد الاتصال

لنقم بإنشاء مثال بسيط لشرح كيفية عمل وظائف رد الاتصال. سننشئ getUser تأخذ معرف المستخدم ووظيفة رد الاتصال كوسائط. ستحاكي وظيفة getUser عملية غير متزامنة باستخدام setTimeout وتنفذ وظيفة رد الاتصال مع بيانات المستخدم بعد تأخير .

// Define the getUser function, which takes a userId and a callback function as arguments
function getUser(userId, callback) {
  // Simulate an asynchronous operation using setTimeout
  setTimeout(() => {
    // Create a mock user object with the provided userId
    const user = {
      id: userId,
      name: 'John Doe',
    };

    // Call the callback function with the user object after a delay
    callback(user);
  }, 1000);
}

// Call the getUser function with a user ID and a callback function to handle the user data
getUser(1, user => {
  // This function will be executed after the getUser function completes its asynchronous operation
  console.log(`User ID: ${user.id}, User Name: ${user.name}`);
});

في هذا المثال ، نستخدم setTimeout لمحاكاة عملية غير متزامنة. تأخذ وظيفة getUser معرف المستخدم ووظيفة رد الاتصال كوسائط. بعد تأخير لمدة ثانية واحدة ، يتم تنفيذ وظيفة رد الاتصال مع بيانات المستخدم.

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

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

عمليات الاسترجاعات - إعادة النظر في setTimeout

سنعيد النظر في setTimeout ونوضح كيف تستخدم وظائف رد الاتصال لتنفيذ التعليمات البرمجية بعد مهلة محددة.

كيف يستخدم setTimeout وظائف رد الاتصال

وظيفة setTimeout هي وظيفة JavaScript مضمنة تسمح لك بتنفيذ وظيفة بعد مهلة محددة. يتطلب حجتين:

  1. وظيفة رد نداء سيتم تنفيذها بعد التأخير

  2. وقت التأخير بالملي ثانية

فيما يلي مثال بسيط لاستخدام setTimeout مع وظيفة رد الاتصال:

// Define a callback function to be executed after a delay
function delayedFunction() {
  console.log('This message is displayed after a 2-second delay');
}

// Use setTimeout to execute the delayedFunction after 2000 milliseconds (2 seconds)
setTimeout(delayedFunction, 2000);

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

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

يمكنك أيضًا استخدام وظائف مجهولة كوظائف رد الاتصال مع setTimeout . فيما يلي مثال على استخدام دالة مجهولة مع setTimeout :

// Use setTimeout to execute an anonymous function after 3000 milliseconds (3 seconds)
setTimeout(function() {
  console.log('This message is displayed after a 3-second delay');
}, 3000);

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

عمليات الاسترجاعات - إعادة النظر في array.filter

الآن سنعيد زيارة array.filter ونوضح كيف تستخدم وظائف رد الاتصال لتصفية عناصر المصفوفة بناءً على شرط محدد.

كيف يستخدم array.filter وظائف رد الاتصال

التصفية مصفوفة في JavaScript تتيح لك إنشاء مصفوفة جديدة باستخدام عناصر تجتاز اختبارًا محددًا (وظيفة رد نداء). تأخذ وظيفة رد الاتصال عنصر مصفوفة كوسيطة وتقوم بإرجاع قيمة منطقية. إذا أعادت دالة رد الاتصال true ، فسيتم تضمين العنصر في المصفوفة الجديدة ؛ خلاف ذلك ، يتم استبعاده.

فيما يلي مثال على استخدام array.filter مع وظيفة رد الاتصال:

const numbers = [1, 2, 3, 4, 5];

// Define a function that will be used as callback function to returns true if the number is even
function isEven(number) {
  return number % 2 === 0;
}

// Use array.filter to create a new array with only the even numbers
const evenNumbers = numbers.filter(isEven);

console.log(evenNumbers); // Output: [2, 4]

اصنع وظيفة صفيف التصفية الخاصة بك

لنقم الآن بإنشاء filterArray تستخدم وظائف رد الاتصال لتصفية العناصر في المصفوفة.

// Define a custom filterArray function that takes an array and a callback function as arguments
function filterArray(array, callback) {
  const newArray = [];

  for (const element of array) {
    if (callback(element)) {
      newArray.push(element);
    }
  }

  return newArray;
}

ضع دالة مصفوفة عامل التصفية المخصصة للاستخدام

الآن بعد أن أصبح لدينا filterArray ، دعنا نطبقها على مصفوفة لتوضيح وظائفها.

const numbers = [1, 2, 3, 4, 5];

// Define a callback function that returns true if the number is greater than 3
function greaterThanThree(number) {
  return number > 3;
}

// Use our custom filterArray function to create a new array with numbers greater than 3
const numbersGreaterThanThree = filterArray(numbers, greaterThanThree);

console.log(numbersGreaterThanThree); // Output: [4, 5]

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

التجربة: ماذا لو جلبت عمليات الاسترجاعات المستخدمة؟

سنناقش تحديات استخدام عمليات الاسترجاعات مع الجلب . سننظر في سيناريو بديل حيث يستخدم الجلب الجلب .

تحديات استخدام الاسترجاعات مع الجلب

الجلب هي وظيفة JavaScript مدمجة حديثة لإجراء طلبات HTTP . تقوم بإرجاع وعد يحل إلى كائن الاستجابة الذي يمثل الاستجابة للطلب.

لنتخيل سيناريو يستخدم فيه الجلب وظائف رد الاتصال بدلاً من الوعود:

function fetchWithCallback(url, successCallback, errorCallback) {
  // Simulate the fetch functionality using callbacks
  // This example is for illustration purposes only
}

fetchWithCallback(
  'https://api.example.com/data',
  function (data) {
    console.log('Success:', data);
  },
  function (error) {
    console.error('Error:', error);
  }
);

في هذا المثال ، قمنا بإنشاء fetchWithCallback تأخذ عنوان URL ووظيفتين لاستدعاء: SuccessCallback الذي يُستدعى عند نجاح الطلب ، وخطأ استدعاء يُستدعى عند فشل الطلب.

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

على سبيل المثال ، تخيل أنك بحاجة إلى إجراء ثلاثة طلبات متسلسلة باستخدام fetchWithCallback :

fetchWithCallback(
  'https://api.example.com/data1',
  function (data1) {
    // Process data1
    fetchWithCallback(
      'https://api.example.com/data2',
      function (data2) {
        // Process data2
        fetchWithCallback(
          'https://api.example.com/data3',
          function (data3) {
            // Process data3
          },
          function (error3) {
            console.error('Error fetching data3:', error3);
          }
        );
      },
      function (error2) {
        console.error('Error fetching data2:', error2);
      }
    );
  },
  function (error1) {
    console.error('Error fetching data1:', error1);
  }
);

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

  1. Callback Hell : مع زيادة عدد العمليات غير المتزامنة ، يمكن أن تصبح الشفرة متداخلة بعمق ، مما يجعل من الصعب قراءتها وصيانتها. يُعرف هذا باسم "رد الاتصال الجحيم".

  2. معالجة الأخطاء : مع عمليات الاسترجاعات ، تصبح معالجة الأخطاء أكثر تعقيدًا ، حيث تحتاج كل وظيفة رد إلى معالجة الأخطاء بشكل منفصل. في المقابل ، تسمح الوعود بمعالجة الأخطاء بشكل مركزي باستخدام .catch () أو .finally () .

  3. التركيب : تسهل الوعود تكوين عمليات متعددة غير متزامنة باستخدام طرق مثل Promise.all () و Promise.race () و Promise.allSettled () . قد يكون تحقيق نفس المستوى من التكوين مع عمليات الاسترجاعات أمرًا مرهقًا وصعبًا.

في الجلب والسيناريو الواقعي ، الذي يستخدم الوعود ، سيبدو المثال نفسه كما يلي:

fetch('https://api.example.com/data1')
  .then((response1) => response.json())
  .then((data1) => {
    console.log('Data from the first request:', data1);

    return fetch('https://api.example.com/data2');
  })
  .then((response2) => response.json())
  .then((data2) => {
    console.log('Data from the second request:', data2);

    return fetch('https://api.example.com/data3');
  })
  .then((response3) => response.json())
  .then((data3) => {
    console.log('Data from the third request:', data3);
  })
  .catch((error) => {
    console.error('Error fetching data:', error);
  });

لذلك نجلب البيانات أولاً من " https://api.example.com/data1 ". بعد نجاح الطلب الأول ، نقوم بتسجيل البيانات والمضي قدمًا في تقديم طلب ثان إلى "https://api.example.com/data2 ". وبالمثل ، عند نجاح الطلب الثاني ، نقوم بتسجيل البيانات وتقديم طلب ثالث إلى " https://api.example.com/data3 ".

أخيرًا ، بعد نجاح الطلب الثالث ، نقوم بتسجيل البيانات. في حالة حدوث أي خطأ أثناء أي من الطلبات ، فإننا نتعامل معه في .catch () .

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

ما هي الوعود وكيف تعمل الوعود في JavaScript

الوعود هي ميزة قوية في JavaScript تساعد في إدارة العمليات غير المتزامنة بشكل أكثر فعالية. يمثل الوعد الإكمال النهائي (أو الفشل) لعملية غير متزامنة وقيمتها الناتجة. الوعد في واحدة من ثلاث حالات:

  1. معلق : الحالة الأولية ؛ لم يتم الوفاء بالوعد ولم يتم رفضه.

  2. تم الوفاء : تمت العملية بنجاح ، وكان الوعد له قيمة ناتجة.

  3. مرفوض : العملية فشلت والوعد له سبب للفشل.

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

كيف يحسنون البرمجة غير المتزامنة

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

  • تبسيط معالجة الأخطاء من خلال التسلسل وإدارة الأخطاء المركزية.

  • تسهيل إنشاء وإدارة عمليات متعددة غير متزامنة.

  • تحسين قابلية قراءة الكود وقابلية صيانته من خلال تجنب عمليات الاسترجاعات المتداخلة.

وعود ثم () التسلسل:

تسمح لك الوعود بربط .then () للتعامل مع نتائج العمليات غير المتزامنة. عند الوفاء بالوعد ، تدخل طريقة . ()

دعنا نستكشف مثالًا أعمق لتسلسل .then () مع Promises باستخدام الجلب لتقديم طلبات API.

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

في هذا المثال ، سنستخدم JSONPlaceholder API لجلب معلومات المستخدم ثم جلب منشورات المستخدم.

const fetch = require("node-fetch");
// Fetch user data from the JSONPlaceholder API
fetch('https://jsonplaceholder.typicode.com/users/1')
  .then((response) => {
    // Check if the request was successful
    if (!response.ok) {
      throw new Error('Failed to fetch user data');
    }
    // Parse the JSON data from the response
    return response.json();
  })
  .then((user) => {
    console.log('User data:', user);

    // Fetch the user's posts using their ID
    return fetch(`https://jsonplaceholder.typicode.com/posts?userId=${user.id}`);
  })
  .then((response) => {
    // Check if the request was successful
    if (!response.ok) {
      throw new Error('Failed to fetch user posts');
    }
    // Parse the JSON data from the response
    return response.json();
  })
  .then((posts) => {
    console.log('User posts:', posts);
  })
  .catch((error) => {
    // Handle any errors during the fetch operations
    console.error('Error:', error);
  });

نقوم أولاً بإحضار بيانات المستخدم من JSONPlaceholder API. عند نجاح الطلب الأول ، نقوم بتحليل بيانات JSON وتسجيل معلومات المستخدم. بعد ذلك ، نستخدم معرف المستخدم لجلب مشاركاته. عند نجاح الطلب الثاني ، نقوم بتحليل بيانات JSON وتسجيل منشورات المستخدم. إذا حدث خطأ أثناء أي من عمليات الجلب ، فإننا نتعامل معه في .catch () .

كيفية استخدام Promise.all () والتعامل مع الوعود المرفوضة

Promise.all () هي طريقة تأخذ مصفوفة (أو أي متكرر) من الوعود وتعيد وعدًا جديدًا يتم الوفاء به مع مجموعة من القيم التي تم الوفاء بها لوعود الإدخال ، بنفس ترتيب وعود الإدخال. يتم الوفاء بالوعد المرتجع فقط عندما يتم الوفاء بجميع وعود الإدخال ، ويتم رفضه إذا تم رفض أي من وعود الإدخال.

إليك كيفية استخدام Promise.all () :

  1. قم بإنشاء مصفوفة (أو أي مجموعة متكررة) من الوعود.

  2. مرر المتكرر إلى Promise.all () .

  3. استخدم .then () للتعامل مع مصفوفة القيم المستوفاة ، أو .catch () للتعامل مع الرفض.

لنلقِ نظرة على مثال باستخدام Promise.all () مع الجلب لعمل طلبات متعددة لواجهة برمجة التطبيقات:

const fetch = require("node-fetch");
// An array of URLs to fetch
const urls = [
  'https://jsonplaceholder.typicode.com/users/1',
  'https://jsonplaceholder.typicode.com/users/2',
  'https://jsonplaceholder.typicode.com/users/3',
];

// Create an array of Promises using fetch()
const fetchPromises = urls.map((url) => fetch(url));

// Use Promise.all() to wait for all fetch() Promises to be fulfilled
Promise.all(fetchPromises)
  .then((responses) => {
    // Map the responses to Promises that resolve with the parsed JSON data
    return Promise.all(responses.map((response) => response.json()));
  })
  .then((users) => {
    console.log('Fetched users:', users);
  })
  .catch((error) => {
    console.error('Error fetching users:', error);
  });

في هذا المثال ، لدينا مجموعة من عناوين URL لجلب بيانات المستخدم. نقوم بإنشاء مجموعة من الوعود باستخدام fetch () لكل عنوان URL. دعنا الآن نلقي نظرة على تقنيات أكثر تقدمًا للتعامل مع التعليمات البرمجية غير المتزامنة في جافا سكريبت.

كلمات Async / انتظار؟

Async / await هي طريقة أكثر حداثة ونظافة من الناحية التركيبية للعمل مع التعليمات البرمجية غير المتزامنة في JavaScript. إنه مبني على قمة الوعود ويجعل رمزك غير المتزامن يبدو ويتصرف مثل الكود المتزامن.

غير متزامن:

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

const fetch = require("node-fetch");
async function asyncFunction() {
  return 'Hello, async!';
}

asyncFunction()
  .then((result) => console.log(result)) // "Hello, async!"
  .catch((error) => console.error(error));

انتظر:

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

استخدام "انتظار" كتابة تعليمات برمجية غير متزامنة تبدو مثل التعليمات البرمجية المتزامنة وتتصرف بها ، مما يسهل قراءتها وفهمها.

فيما يلي مثال على استخدام async / wait مع الجلب لتقديم طلب API:

const fetch = require("node-fetch");
async function fetchUserData() {
  try {
    // Fetch user data from the JSONPlaceholder API
    const response = await fetch('https://jsonplaceholder.typicode.com/users/1');

    // Check if the request was successful
    if (!response.ok) {
      throw new Error('Failed to fetch user data');
    }

    // Parse the JSON data from the response
    const user = await response.json();
    console.log('User data:', user);
 
} catch (error) {
    // Handle any errors during the fetch operation
    console.error('Error:', error);
    }
}

// Call the fetchUserData function
fetchUserData();

نعلن عن دالة "غير متزامنة" تسمى `fetchUserData ()`. داخل هذه الوظيفة ، نستخدم الكلمة الأساسية "انتظار" لانتظار حل طلب "الجلب" قبل المتابعة. نتحقق بعد ذلك مما إذا كان الطلب ناجحًا ونحلل بيانات JSON من الاستجابة باستخدام "انتظار" أيضًا. إذا حدث خطأ أثناء أي من عمليات الجلب ، فإننا نتعامل معه في كتلة "catch". يسهّل النمط غير المتزامن / انتظار الشفرة قراءة وفهم الكود بإزالة الحاجة إلى التسلسل ".then ()" و ".catch ()`. يسمح لك بكتابة المزيد من التعليمات البرمجية الخطية والمتزامنة مع الحفاظ على فوائد العمليات غير المتزامنة.

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

عدم التزامن / الانتظار في الحلقات

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

سنناقش كيفية استخدام غير متزامن / انتظار مع الحلقات وسنقدم أمثلة لتوضيح الأساليب الصحيحة وغير الصحيحة.

نهج غير صحيح مع forEach:

const fetch = require("node-fetch");
async function fetchAllUsers(users) {
  users.forEach(async (userId) => {
    const user = await fetchUserData(userId);
    console.log('User data:', user);
  });

  console.log('Finished fetching all users');
}

fetchAllUsers([1, 2, 3, 4]);

حلقة forEach جميع الوظائف غير المتزامنة بشكل متزامن ، وسترى رسالة "تم الانتهاء من جلب جميع المستخدمين" قبل جلب جميع بيانات المستخدم وتسجيلها.

النهج الصحيح باستخدام for… of loop:

const fetch = require("node-fetch");
async function fetchUserData(userId) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);

  if (!response.ok) {
    throw new Error('Failed to fetch user data');
  }

  return await response.json();
}

async function fetchAllUsers(users) {
  for (const userId of users) {
    const user = await fetchUserData(userId);
    console.log('User data:', user);
  }

  console.log('Finished fetching all users');
}

fetchAllUsers([1, 2, 3, 4]);

نستخدم for… of لتنفيذ وظيفة async fetchUserData () بالتسلسل لكل معرف مستخدم. لن يتم تسجيل رسالة "تم الانتهاء من جلب جميع المستخدمين" إلا بعد جلب جميع بيانات المستخدم وتسجيلها.

استخدام for… of loop أو منتظم for loop باستخدام نظام الانتظار داخل جسم الحلقة ، مما يضمن أن كل تكرار للحلقة ينتظر اكتمال العملية غير المتزامنة السابقة قبل الانتقال إلى التالية. هذا يجلب هذا السؤال إلى الذهن.

النهج الصحيح باستخدام الخريطة () و Promise.all () :

حالة الاستخدام الشائع للجمع بين عدم التزامن / انتظار مع الخريطة () هي عندما تريد إجراء طلبات متعددة غير متزامنة وتنتظر حتى تنتهي جميعها قبل معالجة النتائج. في مثل هذه الحالات ، يمكنك استخدام Promise.all () جنبًا إلى جنب مع map () وغير متزامن / انتظار.

فيما يلي مثال على استخدام غير متزامن / انتظار مع الخريطة () و Promise.all () :

const fetch = require("node-fetch");

async function fetchUser(userId) {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
  const user = await response.json();
  return user;
}

async function fetchMultipleUsers() {
  const userIds = [1, 2, 3, 4, 5];

  const userPromises = userIds.map(async (userId) => {
    const user = await fetchUser(userId);
    return user;
  });

  const users = await Promise.all(userPromises);
  console.log(users);
}

fetchMultipleUsers();

في هذا المثال ، لدينا وظيفة غير متزامنة fetchUser () تجلب مستخدمًا من واجهة برمجة تطبيقات JSONPlaceholder. لدينا أيضًا وظيفة غير متزامنة fetchMultipleUsers () تجلب عدة مستخدمين باستخدام fetchUser () .

داخل fetchMultipleUsers () ، نستخدم map () لإنشاء مجموعة من الوعود عن طريق استدعاء fetchUser () لكل معرف مستخدم. بعد ذلك ، نستخدم Promise.all () لانتظار حل جميع الوعود قبل تسجيل النتيجة.

هل Async / Await في Loops مثل استخدام Promise.all () ؟

لا ، Promise.all () ونهج "Asynchronous Awaits in Synchronous Loops" يخدم أغراضًا مختلفة وليست متماثلة.

Promise.all (): عندما تريد تنفيذ عدة عمليات غير متزامنة بشكل متزامن ، مما يعني أنك تريد تشغيلها في نفس الوقت والانتظار حتى تكتمل جميع العمليات قبل المتابعة ، يمكنك استخدام Promise.all () . يأخذ مجموعة من الوعود ويعيد وعدًا جديدًا يتم حله بمجموعة من القيم التي تم حلها بنفس ترتيب وعود الإدخال.

غير متزامن ينتظر في الحلقات المتزامنة: عندما تريد تنفيذ عدة عمليات غير متزامنة بالتتابع ، مما يعني أنك تريد أن تكتمل كل عملية قبل الانتقال إلى العملية التالية ، فستستخدم الطريقة الصحيحة لـ "انتظار غير متزامن في حلقات متزامنة" (باستخدام غير متزامن / انتظار مع for… of loop أو منتظم for loop).

فيما يلي ملخص للاختلافات:

  1. Promise.all () للتنفيذ المتزامن لوعود متعددة ، بينما يتم استخدام عدم التزامن / انتظار في حلقات متزامنة للتنفيذ المتسلسل لوعود متعددة.

  2. Promise.all () وعدًا واحدًا يتم حله عندما يتم حل جميع وعود الإدخال أو رفضها إذا تم رفض أي من وعود الإدخال ، بينما يسمح لك غير المتزامن / انتظار في حلقات متزامنة بمعالجة الأخطاء بشكل فردي لكل وعد داخل الحلقة.

لاحظ أنك ستختار من بين هذه الأساليب بناءً على ما إذا كنت بحاجة إلى تنفيذ متزامن أو متتابع لعملياتك غير المتزامنة.

البيانات والتحليل والمحاسب

خاتمة:

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

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

https://ahmedradwan.dev

تواصل معنا إذا كنت ترغب في الانضمام إلي وكتابة مقالات مع المهووسين 🙂


اترك ردًا

لن يتم نشر عنوان بريدك الإلكتروني. الحقول المطلوبة محددة *