برنامه نویسی ناهمگام (Async Programming)
برنامه نویسی ناهمگام یعنی کارها همزمان جلو بروند. بنابراین رابط کاربری قفل نمی شود. اینجا با تایپ اسکریپت، همه چیز تایپ دار و مطمئن می شود.
وعده ها (Promises) در تایپ اسکریپت
«وعده (Promise)» یعنی قول یک نتیجه آینده. جنس نتیجه را با Promise<T> مشخص می کنیم.
const fetchGreeting = (): Promise<string> => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('Hello, TypeScript!');
} else {
reject(new Error('Failed to fetch greeting'));
}
}, 1000);
});
};
fetchGreeting()
.then((greeting: string) => {
console.log(greeting.toUpperCase());
})
.catch((error: Error) => {
console.error('Error:', error.message);
});
async/await با تایپ اسکریپت
«async/await» نوشتن کد ناهمگام را ساده می کند. مانند خواندن کد عادی است.
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
}
async function fetchUsers(): Promise<User[]> {
console.log('Fetching users...');
await new Promise((resolve) => setTimeout(resolve, 1000));
return [
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin' },
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'user' }
];
}
async function processUsers(): Promise<User[]> {
try {
const users = await fetchUsers();
console.log('Fetched ' + users.length + ' users');
const adminEmails = users
.filter((u) => u.role === 'admin')
.map((u) => u.email);
console.log('Admin emails:', adminEmails);
return users;
} catch (error) {
console.error('Failed to process users:', (error as Error).message);
throw error;
}
}
processUsers()
.then(() => console.log('Processing complete'))
.catch((err) => console.error('Processing failed:', err));
نکته: همه توابع async یک Promise برمی گردانند. بنابراین نتیجه همیشه Promise است.
Promise.all برای اجرای موازی
چند کار را باهم شروع کن. سپس همه با هم تمام شوند.
interface Product {
id: number;
name: string;
price: number;
}
async function fetchProduct(id: number): Promise<Product> {
await new Promise((resolve) => setTimeout(resolve, Math.random() * 1000));
return { id: id, name: 'Product ' + id, price: Math.floor(Math.random() * 100) };
}
async function fetchMultipleProducts(): Promise<void> {
try {
const [p1, p2, p3] = await Promise.all([
fetchProduct(1),
fetchProduct(2),
fetchProduct(3)
]);
const total = [p1, p2, p3].reduce((sum, p) => sum + p.price, 0);
console.log('Total price: $' + total.toFixed(2));
} catch (error) {
console.error('Error fetching products:', error);
}
}
fetchMultipleProducts();
Promise.race برای زمان سنج
با «race» اولین نتیجه مهم است. برای تایم اوت عالی است.
const timeout = (ms: number): Promise<never> => {
return new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout after ' + ms + 'ms')), ms);
});
};
async function fetchWithTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
return Promise.race([
promise,
timeout(timeoutMs).then(() => {
throw new Error('Request timed out after ' + timeoutMs + 'ms');
})
]);
}
Promise.allSettled برای گزارش کامل
می خواهی همه تمام شوند. چه موفق، چه ناموفق. از «allSettled» استفاده کن.
const fetchData = async (id: number): Promise<{ id: number; data: string }> => {
if (Math.random() > 0.7) {
throw new Error('Failed to fetch data for ID ' + id);
}
return { id: id, data: 'Data for ' + id };
};
async function processBatch(ids: number[]): Promise<{ successful: unknown[]; failed: unknown[] }> {
const tasks = ids.map((id) => fetchData(id));
const results = await Promise.allSettled(tasks);
const successful = results.filter((r) => r.status === 'fulfilled');
const failed = results.filter((r) => r.status === 'rejected');
console.log('Successfully processed: ' + successful.length);
console.log('Failed: ' + failed.length);
return { successful: successful, failed: failed };
}
processBatch([1, 2, 3, 4, 5]);
کال بک های تایپ دار (Callbacks)
در کدهای قدیمی، «کال بک (Callback)» رایج است. تایپ دادن، خطاها را کم می کند.
type FetchCallback = (error: Error | null, data?: string) => void;
function fetchDataWithCallback(url: string, callback: FetchCallback): void {
setTimeout(() => {
try {
callback(null, 'Response data');
} catch (error) {
const err = error instanceof Error ? error : new Error('Unknown error');
callback(err);
}
}, 1000);
}
fetchDataWithCallback('https://api.example.com', (error, data) => {
if (error) {
console.error('Error:', error.message);
return;
}
if (data) {
console.log(data.toUpperCase());
}
});
مدیریت خطا با کلاس های اختصاصی
برای خطاهای دامنه، کلاس بساز. پیام ها واضح تر و کنترل بهتر می شود.
class AppError extends Error {
constructor(message: string, public readonly code: string, public readonly details?: unknown) {
super(message);
this.name = this.constructor.name;
}
}
class NetworkError extends AppError {
constructor(message: string, details?: unknown) {
super(message, 'NETWORK_ERROR', details);
}
}
class ValidationError extends AppError {
constructor(public readonly field: string, message: string) {
super(message, 'VALIDATION_ERROR', { field: field });
}
}
تکرار ناهمگام (Async Iteration)
با «مولد ناهمگام (Async Generator)» داده ها را قطره ای بگیر. سپس مصرف کن.
async function* generateNumbers(): AsyncGenerator<number, void, unknown> {
let i = 0;
while (i < 5) {
await new Promise((resolve) => setTimeout(resolve, 1000));
yield i;
i = i + 1;
}
}
async function consumeNumbers(): Promise<void> {
for await (const num of generateNumbers()) {
console.log(num * 2);
}
}
گام های عملی
- مشکل را به کارهای کوچک بشکن.
- برای هر کار یک Promise بساز.
- با async/await آن ها را ترکیب کن.
- خطاها را با try/catch مدیریت کن.
- برای موازی سازی از Promise.all استفاده کن.
جمع بندی سریع
- async همیشه Promise برمی گرداند.
- await کد را خواناتر می کند.
- all برای موازی سازی است.
- race برای تایم اوت عالی است.
- allSettled گزارش کامل می دهد.
نکته: اگر نوع ها پیچیده شدند، نوع های میانجی تعریف کن. سپس کد تمیزتر می شود.
صفحات مرتبط: ادغام اعلان ها، دکوراتورها. همچنین برای تاکید سئو: برنامه نویسی ناهمگام.