لغة TypeScript

تايب سكريبت غير المتزامنة

38 دقيقة الدرس 22 من 40

تايب سكريبت غير المتزامنة

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

فهم Promises المكتوبة

في تايب سكريبت، Promises هي أنواع عامة تحدد نوع القيمة التي تحلها:

// أنواع Promise الأساسية const stringPromise: Promise<string> = Promise.resolve('مرحباً'); const numberPromise: Promise<number> = Promise.resolve(42); const voidPromise: Promise<void> = Promise.resolve(); // دالة تعيد Promise مكتوبة function fetchUser(id: number): Promise<User> { return fetch(\`/api/users/${id}\`) .then(response => response.json()) .then(data => data as User); } // Promise مع أنواع اتحاد function getDataOrError(): Promise<string | Error> { return fetch('/api/data') .then(response => { if (response.ok) { return response.text(); } return new Error(\`HTTP ${response.status}\`); }); } // إنشاء Promises بكتابة صحيحة function delay(ms: number): Promise<void> { return new Promise((resolve) => { setTimeout(resolve, ms); }); } function waitForValue<T>(value: T, ms: number): Promise<T> { return new Promise((resolve) => { setTimeout(() => resolve(value), ms); }); } // الاستخدام const result: string = await waitForValue('تم', 1000);
ملاحظة: تستنتج تايب سكريبت أنواع Promise تلقائياً في معظم الحالات، لكن الكتابة الصريحة تساعد في توثيق واجهة برمجة التطبيقات الخاصة بك واكتشاف الأخطاء مبكراً.

Async/Await مع تايب سكريبت

الدوال غير المتزامنة في تايب سكريبت تعيد تلقائياً Promise يغلف نوع الإرجاع:

// دالة غير متزامنة تعيد Promise<string> async function fetchUsername(id: number): Promise<string> { const response = await fetch(\`/api/users/${id}\`); const user = await response.json(); return user.name; // تايب سكريبت تعرف أن هذا string } // يتم استنتاج نوع الإرجاع async function getTotal(items: number[]) { await delay(100); return items.reduce((sum, n) => sum + n, 0); } // النوع: Promise<number> // دالة غير متزامنة مع نوع إرجاع اتحاد async function loadConfig(): Promise<Config | null> { try { const response = await fetch('/config.json'); if (!response.ok) return null; return await response.json(); } catch { return null; } } // الدوال غير المتزامنة يمكن أن ترمي أخطاء async function strictFetch(url: string): Promise<Response> { const response = await fetch(url); if (!response.ok) { throw new Error(\`HTTP ${response.status}: ${response.statusText}\`); } return response; } // استخدام await في التعبيرات async function getDisplayName(id: number): Promise<string> { const name = await fetchUsername(id); return \`المستخدم: ${name}\`; }
نصيحة: تايب سكريبت تتحقق من أنه يمكنك استخدام await فقط داخل الدوال غير المتزامنة أو في المستوى الأعلى من وحدات ES، مما يمنع الأخطاء الشائعة.

الدوال غير المتزامنة العامة

العموميات تجعل الدوال غير المتزامنة مرنة وآمنة من حيث النوع:

// غلاف fetch عام async function fetchJSON<T>(url: string): Promise<T> { const response = await fetch(url); if (!response.ok) { throw new Error(\`HTTP ${response.status}\`); } return await response.json() as T; } // الاستخدام مع استنتاج النوع interface User { id: number; name: string; email: string; } interface Post { id: number; title: string; content: string; } const user = await fetchJSON<User>('/api/users/1'); const posts = await fetchJSON<Post[]>('/api/posts'); // تحويل غير متزامن عام async function mapAsync<T, U>( items: T[], fn: (item: T) => Promise<U> ): Promise<U[]> { return Promise.all(items.map(fn)); } // الاستخدام const userIds = [1, 2, 3]; const users = await mapAsync(userIds, id => fetchJSON<User>(\`/api/users/${id}\`)); // منطق إعادة المحاولة العام async function retry<T>( fn: () => Promise<T>, attempts: number = 3, delayMs: number = 1000 ): Promise<T> { for (let i = 0; i < attempts; i++) { try { return await fn(); } catch (error) { if (i === attempts - 1) throw error; await delay(delayMs); } } throw new Error('لا ينبغي الوصول إلى هنا أبداً'); } // الاستخدام const data = await retry(() => fetchJSON<User>('/api/users/1'), 5, 2000); // تنفيذ متوازي عام مع أمان النوع async function parallel<T extends readonly unknown[]>( ...promises: { [K in keyof T]: Promise<T[K]> } ): Promise<T> { return Promise.all(promises) as Promise<T>; } // الاستخدام مع أنواع مختلفة const [user, posts, comments] = await parallel( fetchJSON<User>('/api/users/1'), fetchJSON<Post[]>('/api/posts'), fetchJSON<Comment[]>('/api/comments') ); // user: User, posts: Post[], comments: Comment[]

معالجة الأخطاء غير المتزامنة

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

// Try-catch مع أخطاء مكتوبة async function safeFetch(url: string): Promise<Response> { try { return await fetch(url); } catch (error: unknown) { if (error instanceof TypeError) { console.log('خطأ في الشبكة:', error.message); } else if (error instanceof Error) { console.log('خطأ:', error.message); } throw error; } } // نوع Result للعمليات غير المتزامنة type AsyncResult<T, E = Error> = Promise<Result<T, E>>; type Result<T, E> = | { success: true; value: T } | { success: false; error: E }; async function fetchUserSafe(id: number): AsyncResult<User> { try { const response = await fetch(\`/api/users/${id}\`); if (!response.ok) { return { success: false, error: new Error(\`HTTP ${response.status}\`) }; } const user = await response.json(); return { success: true, value: user }; } catch (error) { return { success: false, error: error instanceof Error ? error : new Error(String(error)) }; } } // استخدام fetch الآمن const result = await fetchUserSafe(123); if (result.success) { console.log('المستخدم:', result.value.name); } else { console.log('خطأ:', result.error.message); } // فئات أخطاء مكتوبة للعمليات غير المتزامنة class NetworkError extends Error { constructor( message: string, public readonly statusCode?: number ) { super(message); this.name = 'NetworkError'; } } class ValidationError extends Error { constructor( message: string, public readonly fields: Record<string, string[]> ) { super(message); this.name = 'ValidationError'; } } async function createUser(data: unknown): Promise<User> { const response = await fetch('/api/users', { method: 'POST', body: JSON.stringify(data) }); if (response.status === 400) { const errors = await response.json(); throw new ValidationError('فشل التحقق', errors); } if (!response.ok) { throw new NetworkError(\`HTTP ${response.status}\`, response.status); } return await response.json(); } // معالجة أنواع أخطاء محددة try { const user = await createUser({ name: 'أحمد' }); } catch (error) { if (error instanceof ValidationError) { console.log('أخطاء التحقق:', error.fields); } else if (error instanceof NetworkError) { console.log(\`خطأ في الشبكة: ${error.statusCode}\`); } else { console.log('خطأ غير معروف'); } }
تحذير: اكتب دائماً متغيرات شرط catch كـ unknown واستخدم حراس الأنواع. لا تفترض أبداً نوع الخطأ دون التحقق.

مجمعات Promise

توفر تايب سكريبت استنتاج نوع ممتاز لمجمعات Promise:

// Promise.all مع أنواع tuple const [user, posts, profile] = await Promise.all([ fetchJSON<User>('/api/users/1'), fetchJSON<Post[]>('/api/posts'), fetchJSON<Profile>('/api/profile') ]); // الأنواع: [User, Post[], Profile] // Promise.race مع أنواع اتحاد const result = await Promise.race([ fetchJSON<User>('/api/users/1'), delay(5000).then(() => null) ]); // النوع: User | null // Promise.allSettled مع نتائج مكتوبة const results = await Promise.allSettled([ fetchJSON<User>('/api/users/1'), fetchJSON<Post[]>('/api/posts'), fetchJSON<Comment[]>('/api/comments') ]); results.forEach((result, index) => { if (result.status === 'fulfilled') { console.log(\`Promise ${index} نجح:\`, result.value); } else { console.log(\`Promise ${index} فشل:\`, result.reason); } }); // Promise.any مع كتابة صحيحة try { const firstUser = await Promise.any([ fetchJSON<User>('/api/users/1'), fetchJSON<User>('/api/users/2'), fetchJSON<User>('/api/users/3') ]); // النوع: User } catch (error) { if (error instanceof AggregateError) { console.log('فشلت جميع الطلبات:', error.errors); } } // مجمع مخصص مع كتابة صحيحة async function firstSuccessful<T>( promises: Promise<T>[] ): Promise<T | null> { const results = await Promise.allSettled(promises); const fulfilled = results.find(r => r.status === 'fulfilled'); return fulfilled && fulfilled.status === 'fulfilled' ? fulfilled.value : null; }

المكررات والمولدات غير المتزامنة

تايب سكريبت تدعم المكررات غير المتزامنة مع أمان نوع كامل:

// واجهة مكرر غير متزامن interface AsyncIterableIterator<T> { next(): Promise<IteratorResult<T>>; [Symbol.asyncIterator](): AsyncIterableIterator<T>; } // دالة مولد غير متزامنة async function* generateNumbers(count: number): AsyncGenerator<number> { for (let i = 0; i < count; i++) { await delay(100); yield i; } } // استخدام التكرار غير المتزامن for await (const num of generateNumbers(5)) { console.log(num); // 0, 1, 2, 3, 4 } // مولد غير متزامن للبيانات المقسمة async function* fetchAllUsers(): AsyncGenerator<User> { let page = 1; let hasMore = true; while (hasMore) { const response = await fetch(\`/api/users?page=${page}\`); const data: { users: User[]; hasMore: boolean } = await response.json(); for (const user of data.users) { yield user; } hasMore = data.hasMore; page++; } } // معالجة البيانات المتدفقة for await (const user of fetchAllUsers()) { console.log(user.name); } // تحويل المكررات غير المتزامنة async function* mapAsyncIterable<T, U>( iterable: AsyncIterable<T>, fn: (item: T) => U | Promise<U> ): AsyncGenerator<U> { for await (const item of iterable) { yield await fn(item); } } // الاستخدام const userNames = mapAsyncIterable( fetchAllUsers(), user => user.name.toUpperCase() ); for await (const name of userNames) { console.log(name); } // تصفية المكررات غير المتزامنة async function* filterAsyncIterable<T>( iterable: AsyncIterable<T>, predicate: (item: T) => boolean | Promise<boolean> ): AsyncGenerator<T> { for await (const item of iterable) { if (await predicate(item)) { yield item; } } } // احصل على المستخدمين النشطين فقط const activeUsers = filterAsyncIterable( fetchAllUsers(), user => user.isActive );
ملاحظة: المولدات غير المتزامنة مثالية لتدفق البيانات ومعالجة مجموعات البيانات الكبيرة أو تنفيذ التصفح مع واجهة برمجة تطبيقات نظيفة.

أدوات غير متزامنة

بناء دوال مساعدة آمنة من حيث النوع لأنماط غير متزامنة شائعة:

// غلاف المهلة async function withTimeout<T>( promise: Promise<T>, timeoutMs: number, timeoutError: Error = new Error('انتهت مهلة العملية') ): Promise<T> { return Promise.race([ promise, delay(timeoutMs).then(() => Promise.reject(timeoutError)) ]); } // الاستخدام const user = await withTimeout( fetchJSON<User>('/api/users/1'), 5000, new Error('انتهت مهلة جلب المستخدم') ); // دالة غير متزامنة مع debounce function debounceAsync<T extends unknown[], R>( fn: (...args: T) => Promise<R>, delayMs: number ): (...args: T) => Promise<R> { let timeoutId: NodeJS.Timeout | null = null; let latestPromise: Promise<R> | null = null; return (...args: T): Promise<R> => { if (timeoutId) { clearTimeout(timeoutId); } latestPromise = new Promise((resolve, reject) => { timeoutId = setTimeout(() => { fn(...args).then(resolve).catch(reject); }, delayMs); }); return latestPromise; }; } // الاستخدام const debouncedSearch = debounceAsync( async (query: string) => fetchJSON<SearchResult[]>(\`/api/search?q=${query}\`), 300 ); // معالجة الدفعات async function batchProcess<T, R>( items: T[], processor: (item: T) => Promise<R>, batchSize: number = 10 ): Promise<R[]> { const results: R[] = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await Promise.all(batch.map(processor)); results.push(...batchResults); } return results; } // معالجة 1000 عنصر في دفعات من 50 const processedItems = await batchProcess( items, async (item) => processItem(item), 50 ); // مساعد التنفيذ التسلسلي async function sequential<T, R>( items: T[], fn: (item: T) => Promise<R> ): Promise<R[]> { const results: R[] = []; for (const item of items) { results.push(await fn(item)); } return results; } // دالة غير متزامنة مع memoization function memoizeAsync<T extends unknown[], R>( fn: (...args: T) => Promise<R> ): (...args: T) => Promise<R> { const cache = new Map<string, Promise<R>>(); return async (...args: T): Promise<R> => { const key = JSON.stringify(args); if (cache.has(key)) { return cache.get(key)!; } const promise = fn(...args); cache.set(key, promise); try { return await promise; } catch (error) { cache.delete(key); throw error; } }; } const cachedFetch = memoizeAsync(fetchJSON<User>);
تمرين:
  1. أنشئ قائمة انتظار غير متزامنة مكتوبة تعالج المهام بشكل تسلسلي مع حدود تزامن قابلة للتكوين.
  2. ابنِ آلية إعادة محاولة مع تراجع أسي وتذبذب، مكتوبة بشكل صحيح لأي دالة غير متزامنة.
  3. نفذ ذاكرة تخزين مؤقت غير متزامنة مع دعم TTL (وقت الحياة) وطرق get/set آمنة من حيث النوع.
  4. أنشئ نظام استطلاع مكتوب يستدعي بشكل متكرر دالة غير متزامنة حتى يتم استيفاء شرط أو انتهاء المهلة.
  5. ابنِ خط أنابيب غير متزامن يربط تحويلات متعددة على البيانات، مع الحفاظ على أمان النوع طوال الوقت.

الخلاصة

تايب سكريبت تحول برمجة جافا سكريبت غير المتزامنة بكتابة قوية لـ Promises، وasync/await، والمكررات غير المتزامنة. من خلال الاستفادة من الدوال غير المتزامنة العامة، وأنماط معالجة الأخطاء المكتوبة، والأدوات المخصصة، يمكنك كتابة كود غير متزامن قوي وقابل للصيانة. نظام الأنواع في تايب سكريبت يلتقط العديد من أخطاء البرمجة غير المتزامنة في وقت الترجمة، مما يجعل كودك غير المتزامن أكثر أماناً وقابلية للتنبؤ.