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

البرمجة غير المتزامنة ضرورية لإنشاء تطبيقات استجابة وكفاءة في جافا سكريبت. TypeScript، وهي مجموعة فوقية من جافا سكريبت، تجعل من الأسهل العمل مع البرمجة غير المتزامنة.

هناك عدة أساليب لـ البرمجة غير المتزامنة في TypeScript، بما في ذلك استخدام الوعود، async/await، و الاستدعاءات. سنغطي كل من هذه الأساليب بالتفصيل حتى تتمكن من اختيار الأفضل لحالتك.

فهرس المحتويات

  1. لماذا تعتبر البرمجة غير المتزامنة مهمة؟

  2. كيف تجعل TypeScript البرمجة غير المتزامنة أسهل

  3. كيفية استخدام الوعود في TypeScript

  4. كيفية استخدام Async / Await في TypeScript

  5. كيفية استخدام الاستدعاءات في TypeScript

  6. الاستنتاج

لماذا تعتبر برمجة الـAsync مهمة؟

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

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

كيف يجعل TypeScript برمجة الـAsync أسهل؟

يوفر TypeScript عدة ميزات تبسط برمجة الـAsync، بما في ذلك سلامة النوع، استنتاج النوع، فحص النوع، وتوضيح النوع.

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

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

وتوضيح النوع في TypeScript يوفر وضوحًا ووثائق لكودك، مما يكون مفيدًا بشكل خاص عند العمل مع وظائف غير متزامنة يمكن أن تكون معقدة للفهم.

الآن دعونا نستعرض ونتعلم عن هذه الميزات الثلاث الرئيسية في البرمجة غير المتزامنة: الوعود، async/await، والاسترجاعات.

كيفية استخدام الوعود في TypeScript

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

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

بمجرد إنشاء الوعد، يمكنك إرفاق استرجاعات له باستخدام طريقة then. ستتم تفعيل هذه الاسترجاعات عندما يتم الوفاء بالوعد، مع القيمة المحلولة المرسلة كمعامل. إذا تم رفض الوعد، يمكنك إرفاق معالج خطأ باستخدام طريقة catch، والتي ستتم استدعاؤها مع سبب الرفض.

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

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

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

كيفية إنشاء وعد

بنية الوعد:

const myPromise = new Promise((resolve, reject) => {
  // قم بتنفيذ عملية غير متزامنة
  // إذا كانت العملية ناجحة، استدعِ resolve مع النتيجة
  // إذا فشلت العملية، استدعِ reject مع كائن الخطأ
});

myPromise
  .then((result) => {
    // التعامل مع النتيجة الناجحة
  })
  .catch((error) => {
    // التعامل مع الخطأ
  });
// المثال 1 حول كيفية إنشاء وعد

function myAsyncFunction(): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    // عملية غير متزامنة
    setTimeout(() => {
      // عملية ناجحة تحل الوعد تحقق من أحدث منشور في مدونتي حول إتقان البرمجة غير المتزامنة في TypeScript! تعلم كيفية العمل مع الوعود، Async/Await، والردود لكتابة كود فعال وقابل للتوسع. استعد لترقية مهاراتك في TypeScript إلى المستوى التالي!
      const success = true;

      if (success) {
        // حل الوعد مع نتيجة العملية إذا كانت العملية ناجحة
        resolve(
          `The result is success and your operation result is ${operationResult}`
        );
      } else {
        const rejectCode: number = 404;
        const rejectMessage: string = `The result is failed and your operation result is ${rejectCode}`;
        // رفض الوعد مع نتيجة العملية إذا فشلت العملية
        reject(new Error(rejectMessage));
      }
    }, 2000);
  });
}

// استخدم الوعد
myAsyncFunction()
  .then((result) => {
    console.log(result); // الناتج: النتيجة هي النجاح ونتيجة عمليتك هي 4
  })
  .catch((error) => {
    console.error(error); // الناتج: النتيجة هي الفشل ونتيجة عمليتك هي 404
  });

في المثال أعلاه، لدينا وظيفة تسمى myAsyncFunction() التي تعيد promise. نستخدم مُنشئ Promise لإنشاء الوعد، الذي يأخذ callback function مع مُعاملات resolve و reject. إذا كانت العملية الغير متزامنة ناجحة، نقوم باستدعاء دالة الـ resolve. إذا فشلت، نقوم باستدعاء دالة الـ reject.

يحتوي الكائن الوعد الذي يتم إرجاعه بواسطة المُنشئ على طريقة then()، التي تأخذ دوال callback للنجاح والفشل. إذا تم حل الوعد بنجاح، يتم استدعاء دالة callback الناجحة مع النتيجة. إذا تم رفض الوعد، يتم استدعاء دالة callback الفاشلة مع رسالة خطأ.

يحتوي الكائن الوعد أيضًا على طريقة catch() تُستخدم للتعامل مع الأخطاء التي تحدث أثناء سلسلة الوعود. تأخذ طريقة catch() دالة callback، التي يتم استدعاؤها إذا حدث أي خطأ في سلسلة الوعود.

الآن، دعنا ننتقل إلى كيفية ربط الوعود في TypeScript.

كيفية ربط الوعود

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

لنلقي نظرة على مثال لكيفية ربط الوعود:

// مثال على كيفية عمل تسلسل الوعود
// الوعد الأول
const promise1 = new Promise((resolve, reject) => {
  const functionOne: string = "This is the first promise function";
  setTimeout(() => {
    resolve(functionOne);
  }, 1000);
});

// الوعد الثاني
const promise2 = (data: number) => {
  const functionTwo: string = "This is the second second promise  function";
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(` ${data}  '+'  ${functionTwo} `);
    }, 1000);
  });
};

// تسلسل الوعود الأول والثاني معًا
promise1
  .then(promise2)
  .then((result) => {
    console.log(result); // الناتج: هذه وظيفة الوعد الأول + هذه وظيفة الوعد الثاني
  })
  .catch((error) => {
    console.error(error);
  });

في المثال أعلاه، لدينا وعدين: promise1 و promise2. يتم حل promise1 بعد 1 ثانية بالسلسلة “هذه وظيفة الوعد الأول.” يأخذ promise2 رقمًا كإدخال ويعيد وعدًا يتم حله بعد 1 ثانية بسلسلة تجمع بين الرقم الذي تم إدخاله والسلسلة “هذه وظيفة الوعد الثاني.”

نقوم بربط الوعدين معًا باستخدام طريقة then. يتم تمرير الناتج promise1 كإدخال لـ promise2. في النهاية، نستخدم طريقة then مرة أخرى لتسجيل ناتج promise2 إلى وحدة التحكم. إذا رفض promise1 أو promise2، سيتم التقاط الخطأ بواسطة طريقة catch.

تهانينا! لقد تعلمت كيفية إنشاء وربط الوعود في TypeScript. يمكنك الآن استخدام الوعود لأداء العمليات الغير متزامنة في TypeScript. الآن، دعنا نستكشف كيفية عمل Async/Await في TypeScript.

كيفية استخدام الـ Async / Await في TypeScript

Async/await هو بنية جديدة تم تقديمها في ES2017 لجعل العمل مع الوعود أسهل. يتيح لك كتابة كود غير متزامن يبدو ويشعر كما لو كان متزامنًا.

في TypeScript، يمكنك تعريف دالة غير متزامنة باستخدام الكلمة الرئيسية async. هذا يخبر المترجم أن الدالة غير متزامنة وستعيد Promise.

الآن، دعنا نرى كيفية استخدام async/await في TypeScript.

بنية Async / Await:

// بنية Async / Await في TypeScript
async function functionName(): Promise<ReturnType> {
  try {
    const result = await promise;
    // الكود لتنفيذه بعد حل الوعد
    return result;
  } catch (error) {
    // الكود لتنفيذه إذا فشل الوعد
    throw error;
  }
}

في المثال أعلاه، functionName هي دالة async تعيد Promise من ReturnType. تستخدم الكلمة الرئيسية await لانتظار حل الوعد قبل الانتقال إلى السطر التالي من الكود.

يُستخدم كتلة try/catch للتعامل مع أي أخطاء تحدث أثناء تشغيل الكود داخل الدالة الغير متزامنة. إذا حدث خطأ، سيتم التقاطه بواسطة كتلة catch، حيث يمكنك التعامل معه بالشكل المناسب.

استخدام الدوال السهمية مع Async / Await

يمكنك أيضًا استخدام الدوال السهمية مع بنية async/await في TypeScript:

const functionName = async (): Promise<ReturnType> => {
  try {
    const result = await promise;
    // الكود لتنفيذه بعد حل الوعد
    return result;
  } catch (error) {
    // الكود لتنفيذه إذا فشل الوعد
    throw error;
  }
};

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

Async / Await مع استدعاء API

الآن، دعونا نتجاوز الصيغة ونحصل على بعض البيانات من واجهة برمجة التطبيقات باستخدام async/await.

interface User {
  id: number;
  name: string;
  email: string;
}

const fetchApi = async (): Promise<void> => {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/users");

    if (!response.ok) {
      throw new Error(
        `Failed to fetch users (HTTP status code: ${response.status})`
      );
    }

    const data: User[] = await response.json();
    console.log(data);
  } catch (error) {
    console.error(error);
    throw error;
  }
};

fetchApi();

هنا، نقوم بجلب البيانات من واجهة برمجة التطبيقات JSONPlaceholder، ثم نحولها إلى JSON، ومن ثم نسجلها في وحدة التحكم. هذا مثال واقعي على كيفية استخدام async/await في TypeScript.

يجب أن تشاهد معلومات المستخدم في وحدة التحكم. تظهر هذه الصورة الناتج:

Async/Await مع استدعاء API باستخدام Axios

// مثال 2 على كيفية استخدام async / await في TypeScript

const fetchApi = async (): Promise<void> => {
  try {
    const response = await axios.get(
      "https://jsonplaceholder.typicode.com/users"
    );
    const data = await response.data;
    console.log(data);
  } catch (error) {
    console.error(error);
  }
};

fetchApi();

في المثال أعلاه، نقوم بتعريف دالة fetchApi() باستخدام async/await وطريقة Axios.get() لإرسال طلب GET HTTP إلى عنوان URL المحدد. نستخدم await لانتظار الاستجابة، ثم استخراج البيانات باستخدام خاصية البيانات في كائن الاستجابة. أخيرًا، نسجل البيانات في وحدة التحكم باستخدام console.log(). يتم التقاط أي أخطاء تحدث وتسجيلها في وحدة التحكم باستخدام console.error().

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

تظهر هذه الصورة الناتج عن استخدام Axios في وحدة التحكم:

ملاحظة: قبل تجربة الكود أعلاه، يجب عليك تثبيت Axios باستخدام npm أو yarn.


npm install axios

yarn add axios

إذا لم تكن على دراية ب Axios، يمكنك معرفة المزيد عنه هنا.

يمكنك أن ترى أننا استخدمنا كتلة try و catch للتعامل مع الأخطاء. تعتبر كتلة try و catch طريقة لإدارة الأخطاء في TypeScript. لذا، كلما قمت بعمليات استدعاء API مثلما فعلنا الآن، تأكد من استخدام كتلة try و catch للتعامل مع أي أخطاء.

الآن، دعنا نستكشف استخدامًا أكثر تقدمًا لكتلة try و catch في TypeScript:

// مثال 3 على كيفية استخدام async / await في TypeScript

interface Recipe {
  id: number;
  name: string;
  ingredients: string[];
  instructions: string[];
  prepTimeMinutes: number;
  cookTimeMinutes: number;
  servings: number;
  difficulty: string;
  cuisine: string;
  caloriesPerServing: number;
  tags: string[];
  userId: number;
  image: string;
  rating: number;
  reviewCount: number;
  mealType: string[];
}

const fetchRecipes = async (): Promise<Recipe[] | string> => {
  const api = "https://dummyjson.com/recipes";
  try {
    const response = await fetch(api);

    if (!response.ok) {
      throw new Error(`Failed to fetch recipes: ${response.statusText}`);
    }

    const { recipes } = await response.json();
    return recipes; // إرجاع مصفوفة الوصفات
  } catch (error) {
    console.error("Error fetching recipes:", error);
    if (error instanceof Error) {
      return error.message;
    }
    return "An unknown error occurred.";
  }
};

// جلب وتسجيل الوصفات
fetchRecipes().then((data) => {
  if (Array.isArray(data)) {
    console.log("Recipes fetched successfully:", data);
  } else {
    console.error("Error message:", data);
  }
});

في المثال أعلاه، نقوم بتعريف interface Recipe التي تحدد هيكل البيانات التي نتوقع الحصول عليها من API. ثم ننشئ دالة fetchRecipes() باستخدام async/await وطريقة fetch() لإجراء طلب GET HTTP إلى نقطة نهاية الAPI المحددة.

نستخدم كتلة try/catch للتعامل مع أي أخطاء قد تحدث أثناء طلب الAPI. إذا كان الطلب ناجحًا، نستخرج خاصية البيانات من الاستجابة باستخدام await ونعيد تشكيلها. إذا حدث خطأ، نتحقق من وجود رسالة خطأ ونعيد إرجاعها كسلسلة إذا كانت موجودة.

أخيرًا، نقوم باستدعاء الدالة fetchRecipes() ونستخدم .then() لتسجيل البيانات المُرجعة إلى وحدة التحكم. يُظهر هذا المثال كيفية استخدام async/await مع كتل try/catch للتعامل مع الأخطاء في سيناريو متقدم أكثر، حيث نحتاج إلى استخراج البيانات من كائن الاستجابة وإرجاع رسالة خطأ مخصصة.

هذه الصورة تُظهر نتيجة الإخراج للشيفرة:

Async / Await with Promise.all

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

// مثال على استخدام async / await مع Promise.all
interface User {
  id: number;
  name: string;
  email: string;
  profilePicture: string;
}

interface Post {
  id: number;
  title: string;
  body: string;
}

interface Comment {
  id: number;
  postId: number;
  name: string;
  email: string;
  body: string;
}

const fetchApi = async <T>(url: string): Promise<T> => {
  try {
    const response = await fetch(url);
    if (response.ok) {
      const data = await response.json();
      return data;
    } else {
      throw new Error(`Network response was not ok for ${url}`);
    }
  } catch (error) {
    console.error(error);
    throw new Error(`Error fetching data from ${url}`);
  }
};

const fetchAllApis = async (): Promise<[User[], Post[], Comment[]]> => {
  try {
    const [users, posts, comments] = await Promise.all([
      fetchApi<User[]>("https://jsonplaceholder.typicode.com/users"),
      fetchApi<Post[]>("https://jsonplaceholder.typicode.com/posts"),
      fetchApi<Comment[]>("https://jsonplaceholder.typicode.com/comments"),
    ]);
    return [users, posts, comments];
  } catch (error) {
    console.error(error);
    throw new Error("Error fetching data from one or more APIs");
  }
};

fetchAllApis()
  .then(([users, posts, comments]) => {
    console.log("Users: ", users);
    console.log("Posts: ", posts);
    console.log("Comments: ", comments);
  })
  .catch((error) => console.error(error));

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

الصورة أدناه تُظهر الناتج من استدعاءات واجهة البرمجة التطبيقية:

دعنا نرى كيفية استخدام Promise.all مع Axios:

// مثال على استخدام async / await مع axios و Promise.all

const fetchApi = async () => {
  try {
    const urls = [
      "https://jsonplaceholder.typicode.com/users",
      "https://jsonplaceholder.typicode.com/posts",
    ];
    const responses = await Promise.all(urls.map((url) => axios.get(url)));
    const data = await Promise.all(responses.map((response) => response.data));
    console.log(data);
  } catch (error) {
    console.error(error);
  }
};

fetchApi();

في المثال أعلاه، نستخدم Promise.all لجلب البيانات من رابطين مختلفين في نفس الوقت. أولاً، نقوم بإنشاء مصفوفة من الروابط، ثم نستخدم الدالة map لإنشاء مصفوفة من الوعود من استدعاءات axios.get. نمرر هذه المصفوفة إلى Promise.all، والتي تعيد مصفوفة من الردود. أخيرًا، نستخدم الدالة map مرة أخرى للحصول على البيانات من كل رد وتسجيلها في وحدة التحكم.

كيفية استخدام الدوال الراجعة في TypeScript

الدالة الراجعة callback هي دالة تُمرر كوسيط إلى دالة أخرى. يتم تنفيذ الدالة الراجعة داخل الدالة الأخرى. تضمن الدوال الراجعة عدم تنفيذ دالة قبل الانتهاء من مهمة – ولكنها تُنفذ مباشرة بعد انتهاء المهمة. تساعدنا في كتابة كود JavaScript غير المتزامن وتمنع المشكلات والأخطاء.

// مثال على استخدام الدوال الراجعة في TypeScript

const add = (a: number, b: number, callback: (result: number) => void) => {
  const result = a + b;
  callback(result);
};

add(10, 20, (result) => {
  console.log(result);
});

تظهر الصورة أدناه دالة الراجعة:

لنرى مثالًا آخر لاستخدام الدوال الراجعة في TypeScript:

// مثال على استخدام دالة راجعة في TypeScript

type User = {
  name: string;
  email: string;
};

const fetchUserData = (
  id: number,
  callback: (error: Error | null, user: User | null) => void
) => {
  const api = `https://jsonplaceholder.typicode.com/users/${id}`;
  fetch(api)
    .then((response) => {
      if (response.ok) {
        return response.json();
      } else {
        throw new Error("Network response was not ok.");
      }
    })
    .then((data) => {
      const user: User = {
        name: data.name,
        email: data.email,
      };
      callback(null, user);
    })
    .catch((error) => {
      callback(error, null);
    });
};

// استخدام fetchUserData مع دالة راجعة
fetchUserData(1, (error, user) => {
  if (error) {
    console.error(error);
  } else {
    console.log(user);
  }
});

في المثال أعلاه، لدينا دالة تُدعى fetchUserData تأخذ id وcallback كوسائط. هذه callback هي دالة بها وسيطان: خطأ ومستخدم.

تقوم وظيفة fetchUserData بجلب بيانات المستخدم من نقطة نهاية API JSONPlaceholder باستخدام الـ id. إذا كان الجلب ناجحًا، فإنه ينشئ كائن User ويمرره إلى وظيفة الاستدعاء مع خطأ فارغ. إذا كان هناك خطأ أثناء الجلب، فيتم إرسال الخطأ إلى وظيفة الاستدعاء مع مستخدم فارغ.

لاستخدام وظيفة fetchUserData مع استدعاء، نقدم الـ id ووظيفة استدعاء كوسيطين. تقوم وظيفة الاستدعاء بالتحقق من الأخطاء وتسجيل بيانات المستخدم إذا لم تكن هناك أخطاء.

الصورة أدناه توضح نتائج استدعاءات الـ API:

كيفية استخدام الاستدعاءات بشكل مسؤول

على الرغم من أن الاستدعاءات أمر أساسي في البرمجة اللامزامنة في TypeScript، إلّا أنها تتطلب إدارة دقيقة لتجنب “جحيم الاستدعاءات” – الشيفرات المتداخلة بشكل هرمي والتي تصعب قراءتها وصيانتها. إليك كيفية استخدام الاستدعاءات بشكل فعّال:

  1. تجنب التداخل العميق

    • قم بتسطيح هيكل الشيفرة الخاصة بك عن طريق تقسيم العمليات المعقدة إلى وظائف مسماة

    • استخدم الوعود (promises) أو async/await لسياقات العمليات اللامزامنة المعقدة (المزيد حول هذا الموضوع أدناه)

  2. معالجة الأخطاء أولاً

    • اتبع دائمًا تقليد Node.js فيما يتعلق بمعلمات (error، result)

    • تحقق من الأخطاء على كل مستوى من التداخلات المتداخلة

    function processData(input: string, callback: (err: Error | null, result?: string) => void) {
      // ... دائما استدعاء التابع مع الخطأ أولاً
    }
  1. استخدم تعليقات الأنواع

    • استفد من نظام الأنواع في TypeScript لفرض توقيعات التابع

    • حدد واجهات واضحة لمعلمات التابع

    type ApiCallback = (error: Error | null, data?: ApiResponse) => void;
  1. افكر في مكتبات تدفق التحكم
    لعمليات الواجب البرمجي المعقدة، استخدم الأدوات مثل async.js لـ:

    • التنفيذ الموازي

    • التنفيذ التسلسلي

    • أنابيب معالجة الأخطاء

متى تستخدم الـ Callbacks مقابل البدائل

هناك أوقات تكون فيها الـ Callbacks خيارًا رائعًا، وأوقات أخرى لا تكون كذلك.

تكون الـ Callbacks مفيدة عندما تعمل مع عمليات الـ async (الاكتفاء الفردي)، والتواصل مع مكتبات أو واجهات برمجة التطبيقات القديمة التي تتوقع الـ Callbacks، والتعامل مع مستمعي الأحداث (مثل مستمعي النقر أو أحداث الويب) أو إنشاء أدوات خفيفة الوزن مع احتياجات بسيطة من الـ async.

في سيناريوهات أخرى حيث تحتاج إلى التركيز على كتابة كود يمكن صيانته مع تدفق واضح لـ async، فإن الـ Callbacks تسبب المشاكل ويجب أن تفضل الوعود أو async-await. على سبيل المثال، عندما تحتاج إلى ربط عمليات متعددة، والتعامل مع انتشار الأخطاء المعقدة، والعمل مع واجهات برمجة التطبيقات الحديثة (مثل واجهة Fetch أو وعود FS)، أو استخدام promise.all() للتنفيذ المتوازي.

مثال على الانتقال من الـ Callbacks إلى الوعود:

// النسخة التي تعتمد على الارجاع
function fetchUser(id: number, callback: (err: Error | null, user?: User) => void) {
  // ... 
}

// النسخة التي تعتمد على الوعد
async function fetchUserAsync(id: number): Promise<User> {
  // ...
}

// الاستخدام مع async/await
try {
  const user = await fetchUserAsync(1);
} catch (error) {
  // التعامل مع الأخطاء
}

تطور أنماط التشغيل الغير متزامن

النمط الإيجابيات السلبيات
الاستدعاءات الارجاعية بسيطة، عالمية تعقيد متداخل
الوعود قابلة للتسلسل، تدفق أخطاء أفضل يتطلب سلاسل .then()
التشغيل الغير متزامن/الانتظار قراءة تماثلية يتطلب ترميزاً

غالباً ما تستخدم مشاريع TypeScript الحديثة مزيجاً: الاستدعاءات الارجاعية لأنماط تحكم الأحداث والوعود/async-await لمنطق التشغيل الغير متزامن المعقد. المفتاح هو اختيار الأداة الصحيحة لحالتك الاستخدامية المحددة مع الحفاظ على وضوح الكود.

الاستنتاج

في هذه المقالة، تعلمنا حول الطرق المختلفة للتعامل مع الكود غير المتزامن في TypeScript. تعلمنا حول الاستدعاءات الارجاعية، الوعود، async/await، وكيفية استخدامها في TypeScript. كما تعلمنا حول هذا المفهوم.

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

شكرًا لقراءة مقالتي. آمل أنك استمتعت بها. إذا كانت لديك أي أسئلة، لا تتردد في التواصل معي.

تواصل معي على وسائل التواصل الاجتماعي: