برنامهنویسی asynchronous روشی برای نوشتن کدی است که میتواند تسکها را به صورت مستقل از یکدیگر اجرا کند، بدون اینکه نیاز باشد یک تسک قبل از شروع تسک دیگر به پایان برسد. وقتی به برنامهنویسی asynchronous فکر میکنیم، مفهوم multitasking و مدیریت مؤثر زمان به ذهنمان میآید.
در این مقاله قصد داریم تا مفهوم async/await در تایپ اسکریپت و برنامهنویسی asynchronous را باهم بررسی کنیم.
قبل از ورود به بحث async/await، لازم است بدانیم که promiseها پایه و اساس برنامهنویسی asynchronous در تایپ اسکریپت و جاوااسکریپت هستند. یک promise نشاندهنده مقداری است که ممکن است بلافاصله در دسترس نباشد، اما در آینده resolve خواهد شد. یک promise میتواند در یکی از سه حالت زیر باشد:
در تایپ اسکریپت، میتوانیم promiseها را به شکل زیر ایجاد و استفاده کنیم:
// Type-safe Promise creation
interface ApiResponse {
data: string;
timestamp: number;
}
const fetchData = new Promise<ApiResponse>((resolve, reject) => {
try {
// Simulating API call
setTimeout(() => {
resolve({
data: "Success!",
timestamp: Date.now()
});
}, 1000);
} catch (error) {
reject(error);
}
});
promiseها را میتوانیم با استفاده از .then() برای عملیات موفق و .catch() برای مدیریت خطا به صورت زنجیرهای اجرا نماییم:
fetchData
.then(response => {
console.log(response.data); // TypeScript knows response has ApiResponse type
return response.timestamp;
})
.then(timestamp => {
console.log(new Date(timestamp).toISOString());
})
.catch(error => {
console.error('Error:', error);
});
در ادامه، مفهوم promiseها را دوباره بررسی خواهیم کرد و نحوه اجرای عملیات asynchronous به صورت parallel را توضیح خواهیم داد.
تایپ اسکریپت یک superset از جاوااسکریپت است، بنابراین async/await دقیقاً همانطور که در جاوااسکریپت کار میکند، در تایپ اسکریپت نیز عمل میکند؛ اما با ویژگیهای اضافی و type safety. تایپ اسکریپت به ما این امکان را میدهد که type safety را برای نتیجه مورد انتظار تضمین کنیم و حتی خطاهای تایپ را بررسی نماییم. این ویژگی به ما کمک میکند تا اشکالات را زودتر در فرآیند توسعه شناسایی کنیم.
async/await در واقع یک سینتکس سادهسازی برای promiseها است، یعنی کلمات کلیدی async و await در واقع یک لایه پوششی روی promiseها هستند. یک تابع async همیشه یک promise را return میکند، حتی اگر مستقیماً از کلمه کلیدی Promise استفاده نکنیم، کامپایلر آن را در یک resolve promise شده قرار میدهد.
به عنوان مثال:
//Snippet 1
const myAsynFunction = async (url: string): Promise<T> => {
const { data } = await fetch(url)
return data
}
//Snippet 2
const immediatelyResolvedPromise = (url: string) => {
const resultPromise = new Promise((resolve, reject) => {
resolve(fetch(url))
})
return resultPromise
}
اگرچه این دو نمونه کد ظاهری متفاوت دارند، اما در اصل عملکرد آنها مشابه است.
async/await به ما این امکان را میدهد تا کدی که داریم را به صورت همگامتر بنویسیم و promise را در همان خط کد باز کنیم. این ویژگی هنگام کار با الگوهای پیچیده asynchronous بسیار مفید است.
برای استفاده درست و کامل از سینتکس async/await، داشتن درک پایهای از promiseها ضروری میباشد.
همانطور که قبلاً به آن اشاره کردیم، promise نشاندهنده این است که چیزی در یک زمان مشخص اتفاق خواهد افتاد، و این امکان را فراهم میکند که برنامه از نتیجه آن رویداد در آینده برای انجام کارهای دیگر استفاده کند.
برای توضیح بهتر، یک مثال واقعی را بررسی کرده و آن را به شبه کد و سپس کد تایپ اسکریپت تبدیل میکنیم.
فرض کنید ما یک چمنزار داریم که باید چمنهای آن را کوتاه کنیم. با یک شرکت تماس میگیریم که قول میدهد چمنها را در عرض چند ساعت کوتاه کند. در مقابل، ما نیز قول میدهیم که بلافاصله پس از کوتاه شدن چمنها هزینه را پرداخت نماییم، به شرطی که کار به درستی انجام شده باشد.
آیا الگوی خاصی را در این فرآیند مشاهده میکنیم؟ اولین نکته آشکار این است که رویداد دوم (پرداخت هزینه) کاملاً به رویداد اول (کوتاه شدن چمنها) وابسته است. اگر promise رویداد اول انجام شود، promise رویداد بعدی اجرا خواهد شد. در این مرحله، promise میتواند resolve شود، reject شود، یا در حالت pending باقی بماند.
در این کد، ما دو promise تعریف کردهایم: یکی که مربوط به شرکت است و دیگری مربوط به خودمان. promise شرکت بعد از ۱۰۰,۰۰۰ میلیثانیه یا resolve میشود یا reject میگردد. هر Promise در یکی از سه وضعیت قرار دارد: resolved (انجامشده) اگر مشکلی وجود نداشته باشد، rejected (رد شده) اگر خطایی رخ دهد، یا pending (در انتظار) اگر هنوز تصمیمگیری نشده باشد. در مثال ما، این وضعیت بعد از 100000 میلیثانیه مشخص خواهد شد.
// I send a request to the company. This is synchronous
// company replies with a promise
const angelMowersPromise = new Promise<string>((resolve, reject) => {
// a resolved promise after certain hours
setTimeout(() => {
resolve('We finished mowing the lawn')
}, 100000) // resolves after 100,000ms
reject("We couldn't mow the lawn")
})
const myPaymentPromise = new Promise<Record<string, number | string>>((resolve, reject) => {
// a resolved promise with an object of 1000 Euro payment
// and a thank you message
setTimeout(() => {
resolve({
amount: 1000,
note: 'Thank You',
})
}, 100000)
// reject with 0 Euro and an unstatisfatory note
reject({
amount: 0,
note: 'Sorry Lawn was not properly Mowed',
})
})
با استفاده از کلمه کلیدی then میتوانیم promiseها را زنجیرهای اجرا کنیم. این روش مانند دستورات متوالی در زبان طبیعی است، «اول این کار را انجام بده، بعد آن کار و سپس … ».
کد زیر ابتدا angelMowersPromise را اجرا میکند. در صورتی که خطایی وجود نداشته باشد، myPaymentPromise اجرا میشود. در صورت بروز خطا در هر یک از این دو promise، آن خطا در بلاک catch مدیریت خواهد شد:
angelMowersPromise
.then(() => myPaymentPromise.then(res => console.log(res)))
.catch(error => console.log(error))
یکی از کاربردهای رایج promiseها در برنامهنویسی فرانتاند، ارسال درخواستهای شبکهای و مدیریت پاسخها است. در کد زیر، یک درخواست برای دریافت لیست کارمندان از سرور ارسال شده است:
const api = 'http://dummy.restapiexample.com/api/v1/employees'
fetch(api)
.then(response => response.json())
.then(employees => employees.forEach(employee => console.log(employee.id)) // logs all employee id
.catch(error => console.log(error.message))) // logs any error from the promise
گاهی اوقات نیاز داریم چندین promise را همزمان یا به ترتیب اجرا کنیم. در این شرایط، Promise.all یا Promise.race ابزارهای مفیدی هستند. به عنوان مثال، فرض کنید قصد داریم لیست ۱۰۰۰ کاربر GitHub را دریافت کرده و سپس برای هر کدام، یک درخواست جداگانه برای دریافت آواتار ارسال کنیم. در این حالت، انتظار برای هر درخواست به صورت ترتیبی منطقی نیست، بلکه بهتر است همه آنها را به طور همزمان اجرا نماییم. این موضوع را در بخش Promise.all بررسی خواهیم کرد.
سینتکس async/await کار با promiseها را در جاوااسکریپت سادهتر میکند. این امکان را فراهم میکند که بتوانیم promiseها را به روشی خوانا و مشابه کدهای synchronous بنویسیم.
یک تابع async/await همیشه یک Promise را return میکند. حتی اگر از کلمه کلیدی Promise استفاده نکنیم، کامپایلر به طور خودکار تابع را درون یک Promise resolve شده قرار میدهد. این ویژگی باعث میشود که مقدار بازگشتی از یک تابع async را مانند یک Promise در نظر بگیریم، که در مواقعی که نیاز به حل چندین عملیات asynchronous داریم، بسیار مفید است.
همانطور که از نام آن پیداست، async همیشه در کنار await استفاده میشود. به این معنی که فقط میتوانیم از await درون یک تابع async استفاده کنیم. وجود async در ابتدای تابع به کامپایلر اطلاع میدهد که این یک تابع asynchronous است.
اگر بخواهیم promiseهای مثال بالا را به async/await تبدیل کنیم، سینتکس آن به شکل زیر خواهد بود:
const myAsync = async (): Promise<Record<string, number | string>> => {
await angelMowersPromise
const response = await myPaymentPromise
return response
}
همانطور که مشاهده میکنیم، این روش خواناتر است و اجرای کد حس همگام بودن دارد. در اینجا، await منتظر اجرای angelMowersPromise میماند و سپس myPaymentPromise اجرا میشود.
ما در این بخش، از مثال دریافت اطلاعات کارمند برای مشاهده نحوه مدیریت خطا استفاده میکنیم؛ زیرا این عملیات به دلیل درخواستهای شبکهای ممکن است با خطا مواجه شود.
برای مثال، فرض کنید که سرور از دسترس خارج شده یا درخواست ما به درستی ارسال نشده است. در چنین شرایطی، باید اجرای برنامه را متوقف کنیم تا از کرش کردن آن جلوگیری نماییم. سینتکس این کار به شکل زیر خواهد بود:
interface Employee {
id: number
employee_name: string
employee_salary: number
employee_age: number
profile_image: string
}
const fetchEmployees = async (): Promise<Array<Employee> | string> => {
const api = 'http://dummy.restapiexample.com/api/v1/employees'
try {
const response = await fetch(api)
const { data } = await response.json()
return data
} catch (error) {
if (error) {
return error.message
}
}
}
ما این تابع را به عنوان یک تابع async تعریف کردهایم. مقدار بازگشتی را به گونهای در نظر گرفتهایم که یا یک آرایه از کارمندان باشد یا یک رشتهای از پیامهای خطا. بنابراین، تایپ promise در اینجا Promise<Array<Employee> | string> خواهد بود.
درون بلاک try کدهایی قرار میگیرند که انتظار داریم در شرایط عادی و بدون خطا اجرا شوند. در مقابل، بلاک catch هر گونه خطایی که رخ دهد را دریافت و مدیریت میکند. در چنین حالتی، فقط مقدار ویژگی message از آبجکت error را return میکنیم.
نکته جالب اینجاست که هر خطایی که درون بلاک try رخ دهد، بلافاصله throw شده و توسط بلاک catch گرفته میشود. اگر این خطا مدیریت نشود، ممکن است باعث بروز خطاهای سخت برای دیباگ شده یا حتی کل برنامه را متوقف نماید.
در حالی که استفاده از بلاکهای سنتی try/catch برای مدیریت خطاها در سطح لوکال کارآمد است، اما اگر بیش از حد مورد استفاده قرار بگیرد، میتواند کد را شلوغ کرده و منطق اصلی برنامه را تحت تأثیر قرار دهد. در چنین شرایطی، استفاده از توابع Higher-Order راهحل بهتری خواهد بود.
تابع Higher-Order تابعی است که یک یا چند تابع دیگر را به عنوان آرگومان دریافت میکند یا یک تابع را return کند. در زمینه مدیریت خطاها، میتوانیم از یک تابع Higher-Order برای wrapping یک تابع async استفاده کنیم تا خطاهای احتمالی را مدیریت کند. به این ترتیب، منطق try/catch از هسته اصلی برنامه جدا میشود و کد خواناتر و تمیزتر خواهد بود.
ایده اصلی این است که یک تابع Wrapper ایجاد کنیم که یک تابع async/await را همراه با آرگومانهای لازم دریافت کند. درون این Wrapper یک بلاک try/catch پیادهسازی میکنیم تا مدیریت خطاها را به صورت مرکزی انجام دهد. این کار باعث میشود که کد ما خواناتر و نگهداری آن آسانتر شود.
برای درک بهتر این موضوع، مثال دریافت اطلاعات کارمندان را بررسی میکنیم:
// Async function to fetch employee data
async function fetchEmployees(apiUrl: string): Promise<Employee[]> {
const response = await fetch(apiUrl);
const data = await response.json();
return data;
}
// Wrapped version of fetchEmployees using the higher-order function
const safeFetchEmployees = (url: string) => handleAsyncErrors(fetchEmployees, url);
// Example API URL
const api = 'http://dummy.restapiexample.com/api/v1/employees';
// Using the wrapped function to fetch employees
safeFetchEmployees(api)
.then(data => {
if (data) {
console.log("Fetched employee data:", data);
} else {
console.log("Failed to fetch employee data.");
}
})
.catch(err => {
// This catch block might be redundant, depending on your error handling strategy within the higher-order function
console.error("Error in safeFetchEmployees:", err);
});
در این مثال، تابع safeFetchEmployees از تابع Higher-Order handleAsyncErrors برای wrap کردن تابع fetchEmployees اصلی استفاده میکند.
این روش به طور خودکار خطاهایی را که ممکن است در طول API call رخ دهد کنترل میکند، آنها را ثبت کرده و برای نشان دادن وضعیت خطا، null را برمیگرداند. سپس مصرفکننده safeFetchEmployees میتواند بررسی کند که آیا مقدار بازگشتی تهی است یا خیر، تا تعیین کند که آیا عملیات موفقیتآمیز بوده یا خطایی رخ داده است.
همانطور که قبلاً به آن اشاره کردیم، گاهی اوقات نیاز داریم که promiseها به صورت همزمان اجرا شوند.
بیایید یک مثال از API کارمندان را بررسی کنیم. فرض کنید ابتدا باید همه کارمندان را دریافت کنیم، سپس نام آنها را استخراج کرده و در نهایت یک ایمیل از نامشان تولید نماییم. بدیهی است که باید این توابع را هم به صورت متوالی و هم به طور همزمان اجرا کنیم تا یکی باعث متوقف شدن دیگری نشود.
در این شرایط، میتوانیم از Promise.all استفاده کنیم. طبق گفته Mozilla «Promise.all معمولاً زمانی استفاده میشود که چندین عملیات asynchronous به طور همزمان اجرا شدهاند و ما میخواهیم منتظر بمانیم تا همهی آنها تکمیل شوند».
/employeeid از هر کاربر، و دریافت اطلاعات هر کاربر: /employee/{id}const baseApi = 'https://reqres.in/api/users?page=1'
const userApi = 'https://reqres.in/api/user'
const fetchAllEmployees = async (url: string): Promise<Employee[]> => {
const response = await fetch(url)
const { data } = await response.json()
return data
}
const fetchEmployee = async (url: string, id: number): Promise<Record<string, string>> => {
const response = await fetch(`${url}/${id}`)
const { data } = await response.json()
return data
}
const generateEmail = (name: string): string => {
return `${name.split(' ').join('.')}@company.com`
}
const runAsyncFunctions = async () => {
try {
const employees = await fetchAllEmployees(baseApi)
Promise.all(
employees.map(async user => {
const userName = await fetchEmployee(userApi, user.id)
const emails = generateEmail(userName.name)
return emails
})
)
} catch (error) {
console.log(error)
}
}
runAsyncFunctions()
در کد بالا:
fetchEmployees تمام کارمندان را از baseApi دریافت میکند. ابتدا await پاسخ را دریافت کرده، آن را به JSON تبدیل میکند و سپس دادههای پردازش شده را return میکند.fetchEmployee اطلاعات یک کارمند خاص را دریافت میکند.generateEmail بر اساس نام هر کارمند، یک ایمیل تولید میکند.runAsyncFunctions ابتدا تمام کارمندان دریافت میشوند. سپس از Promise.all برای دریافت اطلاعات هر کارمند و تولید ایمیل آنها به صورت همزمان استفاده میکنیم.نکتهی کلیدی این است که داخل تابع async، کدها به ترتیب و خطبهخط اجرا میشوند. اگر قبل از تکمیل یک promise، سعی کنیم دادهی آن را پردازش کنیم، دچار خطا خواهیم شد. در fetchEmployee نیز همین مسئله صادق است؛ تنها اطلاعات یک کارمند را دریافت کرده و پردازش میکنیم.
اگر خطایی رخ دهد، این خطا از promise reject شده به Promise.all منتقل میشود و در نهایت به یک استثنا تبدیل میگردد که در catch مدیریت میشود.
Promise.all زمانی مفید است که نیاز داشته باشیم همه promiseها successe شوند، اما در دنیای واقعی، ممکن است برخی عملیاتها موفق و برخی دیگر ناموفق باشند. به عنوان مثال، در سیستم مدیریت کارمندان، ممکن است بخواهیم چندین رکورد کارمندان را بهروزرسانی کنیم، اما برخی بهروزرسانیها به دلیل خطاهای اعتبارسنجی یا مشکلات شبکهای ناموفق باشند.
در این شرایط، Promise.allSettled بسیار کاربرد دارد. برخلاف Promise.all که در صورت شکست یک promise کل عملیات را متوقف میکند، Promise.allSettled تا زمانی که همه promiseها اجرا شوند، منتظر میماند و نتیجه هر promise را (موفق یا ناموفق) گزارش میدهد. به عنوان مثال سیستم مدیریت کارکنان را با این روش بهروزرسانی میکنیم:
interface UpdateResult {
id: number;
success: boolean;
message: string;
}
const updateEmployee = async (employee: Employee): Promise<UpdateResult> => {
const api = `${userApi}/${employee.id}`;
try {
const response = await fetch(api, {
method: 'PUT',
body: JSON.stringify(employee),
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
return {
id: employee.id,
success: true,
message: 'Update successful'
};
} catch (error) {
return {
id: employee.id,
success: false,
message: error instanceof Error ? error.message : 'Update failed'
};
}
};
const bulkUpdateEmployees = async (employees: Employee[]) => {
const updatePromises = employees.map(emp => updateEmployee(emp));
const results = await Promise.allSettled(updatePromises);
// Process results and generate a report
const summary = results.reduce((acc, result, index) => {
if (result.status === 'fulfilled') {
acc.successful.push(result.value);
} else {
acc.failed.push({
id: employees[index].id,
error: result.reason
});
}
return acc;
}, {
successful: [] as UpdateResult[],
failed: [] as Array<{id: number; error: any}>
});
return summary;
};
Promise.allSettled را میتوانیم مانند یک مدیر پروژه در نظر بگیریم که چندین تسک را پیگیری میکند. برخلاف Promise.all که در صورت شکست یک تسک کل فرآیند را متوقف میکند، این مدیر به کارهای دیگر ادامه داده و در پایان گزارشی از تمام تسکها (موفق و ناموفق) ارائه میدهد.
کاربردهای این روش عبارتند از:
گاهی نیاز داریم حجم زیادی از دادهها را که به صورت دستهای دریافت میشوند، پردازش کنیم. فرض کنید در حال export کردن دادههای کارمندان یک سیستم سازمانی بزرگ هستیم؛ احتمالاً هزاران رکورد داریم که برای جلوگیری از اشغال بیشازحد حافظه، در چندین بخش دریافت میشوند.
حلقه for await...of برای این سناریو ایدهآل است. این حلقه به ما کمک میکند تا دادههای asynchronous را به صورت تکتک پردازش کنیم و کدی بهینه و خوانا داشته باشیم. به عنوان مثال در سیستم مدیریت کارکنان داریم:
interface PaginatedResponse<T> {
data: T[];
nextPage?: string;
}
async function* fetchAllPages<T>(
initialUrl: string,
fetchPage: (url: string) => Promise<PaginatedResponse<T>>
): AsyncIterableIterator<T> {
let currentUrl = initialUrl;
while (currentUrl) {
const response = await fetchPage(currentUrl);
for (const item of response.data) {
yield item;
}
currentUrl = response.nextPage || '';
}
}
// Usage with type safety
async function processAllEmployee() {
const fetchPage = async (url: string): Promise<PaginatedResponse<Employee>> => {
const response = await fetch(url);
return response.json();
};
try {
for await (const employee of fetchAllPages('/api/employees', fetchPage)) {
// Process each employee as they come in
console.log(`Processing employee: ${employee.employee_name}`);
await updateEmployeeAnalytics(employee);
}
} catch (error) {
console.error('Failed to process employees:', error);
}
}
function updateEmployeeAnalytics(employee: Employee) { /** custom logic */}
میتوانیم for await...of را مانند یک نوار نقاله در کارخانه در نظر بگیریم. به جای اینکه صبر کنیم تا همه محصولات تولید شوند و سپس بستهبندی را شروع کنیم، هر محصول را به محض ورود پردازش میکنیم.
مزایای این روش عبارتند از:
ترکیب توابع Higher-Order با async/await، الگوهای قدرتمندی برای مدیریت عملیاتهای asynchronous ایجاد میکند.
در سیستم مدیریت کارمندان، معمولاً باید آرایهای از دادهها را به صورت asynchronous پردازش کنیم. متدهای آرایهای مانند map، filter و reduce هنگام استفاده از async/await چالشهایی دارند که باید به درستی مدیریت شوند. به عنوان مثال:
// Async filter: Keep only active employees
async function filterActiveEmployees(employees: Employee[]) {
const checkResults = await Promise.all(
employees.map(async (employee) => {
const status = await checkEmployeeStatus(employee.id);
return { employee, isActive: status === 'active' };
})
);
return checkResults
.filter(result => result.isActive)
.map(result => result.employee);
}
// Async reduce: Calculate total department salary
async function calculateDepartmentSalary(employeeIds: number[]) {
return await employeeIds.reduce(async (promisedTotal, id) => {
const total = await promisedTotal;
const employee = await fetchEmployeeDetails(id);
return total + employee.salary;
}, Promise.resolve(0)); // Initial value must be a Promise
}
نکات مهمی که هنگام کار با async/await در تایپ اسکریپت و توابع آرایهای باید به آنها توجه داشته باشیم عبارت است از:
map با عملیات async یک آرایه از promiseها تولید میکند که باید با Promise.all مدیریت شوند.filter نیاز به پردازش خاص دارد، زیرا نمیتوانیم مستقیماً نتیجه promise را به عنوان شرط فیلتر استفاده کنیم.reduce هنگام کار با async باید به درستی مقدار تجمیع شده را مدیریت نماید.گاهی نیاز است توابع کمکی خاصی برای عملیاتهای انجام شده روی دادههای asynchronous ایجاد کنیم. میتوانیم توابع Higher-Orderای بسازیم که عملکردهای اضافی مانند کش کردن را به عملیاتهای async اضافه کنند.
بهعنوان مثال، کد زیر از تابع Higher-Order withCache برای افزودن قابلیت کش به یک تابع async که دادهها را بر اساس id دریافت میکند، استفاده مینماید. اگر همان id چندین بار در مدت پنج ثانیه درخواست شود، مقدار کش شده return میشود تا از درخواستهای غیرضروری به شبکه جلوگیری شود.
// Higher-order function for caching async results
function withCache<T>(
asyncFn: (id: number) => Promise<T>,
ttlMs: number = 5000
) {
const cache = new Map<number, { data: T; timestamp: number }>();
return async (id: number): Promise<T> => {
const cached = cache.get(id);
const now = Date.now();
if (cached && now - cached.timestamp < ttlMs) {
return cached.data;
}
const data = await asyncFn(id);
cache.set(id, { data, timestamp: now });
return data;
};
}
// Usage example
const cachedFetchEmployee = withCache(async (id: number) => {
const response = await fetch(`${baseApi}/employee/${id}`);
return response.json();
});
Awaited یک تایپ کمکی است که عملکرد await را در توابع async مدلسازی میکند. این تایپ مقدار resolve یک promise را استخراج میکند و هرگونه لایههای تودرتوی promise را حذف مینماید.
Awaited به ما کمک میکند تایپ مقداری را که پس از await دریافت میکنیم، مشخص نماییم. این کار باعث میشود که تایپ اسکریپت متوجه شود پس از await دیگر با یک promise سروکار نداریم، بلکه با داده واقعی مواجه هستیم.
سینتکس بیسیک آن به صورت زیر میباشد:
type MyPromise = Promise<string>; type AwaitedType = Awaited<MyPromise>; // AwaitedType will be 'string'
Awaited دقیقاً مدل then در promiseها را شبیهسازی نمیکند، اما هنگام استفاده از then در توابع async میتواند مفید باشد. اگر از await درون یک callback مربوط به then استفاده کنیم، Awaited به تعیین تایپ مقدار استخراج شده کمک میکند و نیاز به type annotation اضافی را کاهش میدهد.
async و await در تایپ اسکریپت به ما این امکان را میدهند که کد asynchronous را به صورتی بنویسیم که مانند کد synchronous به نظر برسد. این ویژگی، خوانایی و درک کد را بسیار بهبود میبخشد.
مفاهیم کلیدی که هنگام کار با async/await در تایپ اسکریپت باید به خاطر داشته باشیم عبارتند از:
Promise.all برای اجرای چندین عملیات همزمان مفید است، اما در صورت شکست یکی، کل عملیات را متوقف میکند.Promise.allSettled اجازه میدهد همه promiseها اجرا شوند و نتیجه موفق یا ناموفق آنها را دریافت کنیم.for await...of برای پردازش جریانهای دادهای بزرگ به صورت بهینه و بلادرنگ مناسب است.map, filter, reduce) باید توجه ویژهای به نحوهی مدیریت promiseها داشته باشیم.Awaited به تایپ اسکریپت کمک میکند تایپ مقداری که پس از await به دست میآید را به درستی درک کند.با تسلط بر این مفاهیم، میتوانیم عملیاتهای asynchronous را در تایپ اسکریپت بهصورت کارآمد و خوانا مدیریت نماییم.