بررسی عمیق توابع مولد ناهمگام در جاوا اسکریپت، بررسی پروتکل های تکرار ناهمگام، موارد استفاده و مثال های عملی برای توسعه وب مدرن.
توابع مولد ناهمگام: تسلط بر پروتکل های تکرار ناهمگام
برنامه نویسی ناهمگام یک رکن اساسی در توسعه جاوا اسکریپت مدرن است، به خصوص زمانی که با عملیات ورودی/خروجی مانند دریافت داده از API ها، خواندن فایل ها یا تعامل با پایگاه های داده سروکار داریم. به طور سنتی، ما برای مدیریت این وظایف ناهمگام به Promises و async/await تکیه کرده ایم. با این حال، توابع مولد ناهمگام روشی قدرتمند و ظریف برای رسیدگی به تکرار ناهمگام ارائه می دهند و ما را قادر می سازند تا جریان های داده را به طور ناهمگام و کارآمد پردازش کنیم.
درک پروتکل های تکرار ناهمگام
قبل از پرداختن به توابع مولد ناهمگام، درک پروتکل های تکرار ناهمگام که بر اساس آنها ساخته شده اند ضروری است. این پروتکل ها تعریف می کنند که چگونه منابع داده ناهمگام می توانند به روشی کنترل شده و قابل پیش بینی تکرار شوند.
پروتکل قابل تکرار ناهمگام
پروتکل قابل تکرار ناهمگام، شیئی را تعریف می کند که می تواند به طور ناهمگام تکرار شود. یک شیء در صورتی با این پروتکل مطابقت دارد که متدی داشته باشد که با Symbol.asyncIterator
کلید گذاری شده و یک تکرار کننده ناهمگام را برمی گرداند.
به یک شیء قابل تکرار مانند یک لیست پخش آهنگ فکر کنید. شیء قابل تکرار ناهمگام مانند یک لیست پخش است که در آن هر آهنگ قبل از پخش شدن باید (به طور ناهمگام) بارگیری شود.
مثال:
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
next() {
// Asynchronously fetch the next value
}
};
}
};
پروتکل تکرار کننده ناهمگام
پروتکل تکرار کننده ناهمگام، متدهایی را تعریف می کند که یک تکرار کننده ناهمگام باید پیاده سازی کند. شیئی که با این پروتکل مطابقت دارد باید متد next()
و به صورت اختیاری متدهای return()
و throw()
را داشته باشد.
- next(): این متد یک Promise را برمی گرداند که به شیئی با دو ویژگی
value
وdone
حل می شود.value
حاوی مقدار بعدی در دنباله است وdone
یک مقدار بولی است که نشان می دهد آیا تکرار کامل شده است یا خیر. - return(): (اختیاری) این متد یک Promise را برمی گرداند که به شیئی با ویژگی های
value
وdone
حل می شود. این نشان می دهد که تکرار کننده در حال بسته شدن است. این برای آزاد کردن منابع مفید است. - throw(): (اختیاری) این متد یک Promise را برمی گرداند که با یک خطا رد می شود. برای نشان دادن این است که در طول تکرار خطایی رخ داده است.
مثال:
const asyncIterator = {
next() {
return new Promise((resolve) => {
// Asynchronously fetch the next value
setTimeout(() => {
resolve({ value: /* some value */, done: false });
}, 100);
});
},
return() {
return Promise.resolve({ value: undefined, done: true });
},
throw(error) {
return Promise.reject(error);
}
};
معرفی توابع مولد ناهمگام
توابع مولد ناهمگام روشی راحت تر و خواناتر برای ایجاد تکرار کننده ها و اشیاء قابل تکرار ناهمگام ارائه می دهند. آنها قدرت مولدها را با ناهمزمانی Promises ترکیب می کنند.
نحوه نوشتن
یک تابع مولد ناهمگام با استفاده از نحوه نوشتن async function*
اعلان می شود:
async function* myAsyncGenerator() {
// Asynchronous operations and yield statements here
}
کلمه کلیدی yield
درون یک تابع مولد ناهمگام، کلمه کلیدی yield
برای تولید مقادیر به صورت ناهمگام استفاده می شود. هر دستور yield
به طور موثر اجرای تابع مولد را تا زمانی که Promise حاصل شده حل شود متوقف می کند.
مثال:
async function* fetchUsers() {
const user1 = await fetch('https://example.com/api/users/1').then(res => res.json());
yield user1;
const user2 = await fetch('https://example.com/api/users/2').then(res => res.json());
yield user2;
const user3 = await fetch('https://example.com/api/users/3').then(res => res.json());
yield user3;
}
مصرف مولدهای ناهمگام با for await...of
شما می توانید با استفاده از حلقه for await...of
بر روی مقادیر تولید شده توسط یک تابع مولد ناهمگام تکرار کنید. این حلقه به طور خودکار از عهده حل ناهمگام Promises حاصل شده توسط مولد برمی آید.
مثال:
async function main() {
for await (const user of fetchUsers()) {
console.log(user);
}
}
main();
موارد استفاده عملی برای توابع مولد ناهمگام
توابع مولد ناهمگام در سناریوهایی که شامل جریان های داده ناهمگام هستند، مانند:
1. جریان داده از API ها
تصور کنید که در حال دریافت مجموعه داده بزرگی از یک API هستید که از صفحه بندی پشتیبانی می کند. به جای دریافت کل مجموعه داده به یکباره، می توانید از یک تابع مولد ناهمگام برای دریافت و تولید صفحات داده به صورت افزایشی استفاده کنید.
مثال (دریافت داده های صفحه بندی شده):
async function* fetchPaginatedData(url, pageSize = 10) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}&pageSize=${pageSize}`);
const data = await response.json();
if (data.length === 0) {
return; // No more data
}
for (const item of data) {
yield item;
}
page++;
}
}
async function main() {
for await (const item of fetchPaginatedData('https://api.example.com/data')) {
console.log(item);
}
}
main();
مثال بین المللی (API نرخ ارز):
async function* fetchExchangeRates(currencyPair, startDate, endDate) {
let currentDate = new Date(startDate);
while (currentDate <= new Date(endDate)) {
const dateString = currentDate.toISOString().split('T')[0]; // YYYY-MM-DD
const url = `https://api.exchangerate.host/${dateString}?base=${currencyPair.substring(0,3)}&symbols=${currencyPair.substring(3,6)}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.success) {
yield {
date: dateString,
rate: data.rates[currencyPair.substring(3,6)],
};
}
} catch (error) {
console.error(`Error fetching data for ${dateString}:`, error);
// You might want to handle errors differently, e.g., retry or skip the date.
}
currentDate.setDate(currentDate.getDate() + 1);
}
}
async function main() {
const currencyPair = 'EURUSD';
const startDate = '2023-01-01';
const endDate = '2023-01-10';
for await (const rate of fetchExchangeRates(currencyPair, startDate, endDate)) {
console.log(rate);
}
}
main();
این مثال نرخ ارز روزانه EUR به USD را برای یک محدوده تاریخی مشخص دریافت می کند. این مثال خطاهای احتمالی در طی تماس های API را کنترل می کند. به خاطر داشته باشید که `https://api.exchangerate.host` را با یک نقطه پایانی API معتبر و مناسب جایگزین کنید.
2. پردازش فایل های بزرگ
هنگام کار با فایل های بزرگ، خواندن کل فایل در حافظه می تواند ناکارآمد باشد. توابع مولد ناهمگام به شما امکان می دهند فایل را خط به خط یا در تکه ها بخوانید و هر تکه را به صورت ناهمگام پردازش کنید.
مثال (خواندن یک فایل بزرگ خط به خط - Node.js):
const fs = require('fs');
const readline = require('readline');
async function* readLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function main() {
for await (const line of readLines('large_file.txt')) {
// Process each line asynchronously
console.log(line);
}
}
main();
این مثال Node.js نحوه خواندن یک فایل خط به خط با استفاده از fs.createReadStream
و readline.createInterface
را نشان می دهد. تابع مولد ناهمگام readLines
هر خط را به صورت ناهمگام تولید می کند.
3. رسیدگی به جریان های داده در زمان واقعی (WebSockets، Server-Sent Events)
توابع مولد ناهمگام برای پردازش جریان های داده در زمان واقعی از منابعی مانند WebSockets یا Server-Sent Events (SSE) مناسب هستند. شما می توانید به طور مداوم داده ها را هنگام رسیدن از جریان تولید کنید.
مثال (پردازش داده از یک WebSocket - مفهومی):
// This is a conceptual example and requires a WebSocket library like 'ws' (Node.js) or the browser's built-in WebSocket API.
async function* processWebSocketStream(url) {
const websocket = new WebSocket(url);
websocket.onmessage = (event) => {
//This needs to be handled outside the generator.
//Typically, you'd push the event.data into a queue
//and the generator would asynchronously pull from the queue
//via a Promise that resolves when data is available.
};
websocket.onerror = (error) => {
//Handle errors.
};
websocket.onclose = () => {
//Handle close.
}
//The actual yielding and queue management would happen here,
//making use of Promises to synchronize between the websocket.onmessage
//event and the async generator function.
//This is a simplified illustration.
//while(true){ //Use this if properly queuing events.
// const data = await new Promise((resolve) => {
// // Resolve the promise when data is available in the queue.
// })
// yield data
//}
}
async function main() {
// for await (const message of processWebSocketStream('wss://example.com/ws')) {
// console.log(message);
// }
console.log("WebSocket example - conceptual only. See comments in code for details.");
}
main();
نکات مهم درباره مثال WebSocket:
- مثال WebSocket ارائه شده در درجه اول مفهومی است زیرا ادغام مستقیم ماهیت رویداد محور WebSocket با مولدهای ناهمگام نیازمند همگام سازی دقیق با استفاده از Promises و صف ها است.
- پیاده سازی های دنیای واقعی معمولاً شامل بافر کردن پیام های ورودی WebSocket در یک صف و استفاده از Promise برای سیگنال دادن به مولد ناهمگام هنگام در دسترس بودن داده های جدید است. این تضمین می کند که مولد در حین انتظار برای داده ها مسدود نمی شود.
4. پیاده سازی تکرار کننده های ناهمگام سفارشی
توابع مولد ناهمگام ایجاد تکرار کننده های ناهمگام سفارشی را برای هر منبع داده ناهمگام آسان می کنند. شما می توانید منطق خود را برای دریافت، پردازش و تولید مقادیر تعریف کنید.
مثال (تولید یک دنباله از اعداد به صورت ناهمگام):
async function* generateNumbers(start, end, delay) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, delay));
yield i;
}
}
async function main() {
for await (const number of generateNumbers(1, 5, 500)) {
console.log(number);
}
}
main();
این مثال یک دنباله از اعداد را از start
تا end
با یک delay
مشخص شده بین هر عدد تولید می کند. خط await new Promise(resolve => setTimeout(resolve, delay))
یک تاخیر ناهمگام ایجاد می کند.
رسیدگی به خطا
رسیدگی به خطا هنگام کار با توابع مولد ناهمگام بسیار مهم است. می توانید از بلوک های try...catch
در داخل تابع مولد برای رسیدگی به خطاهایی که در طول عملیات ناهمگام رخ می دهند استفاده کنید.
مثال (رسیدگی به خطا در یک مولد ناهمگام):
async function* fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
yield data;
} catch (error) {
console.error('Error fetching data:', error);
// You can choose to re-throw the error, yield a default value, or stop the iteration.
// For example, yield { error: error.message };
throw error;
}
}
async function main() {
try {
for await (const data of fetchData('https://example.com/api/invalid')) {
console.log(data);
}
} catch (error) {
console.error('Error during iteration:', error);
}
}
main();
این مثال نحوه رسیدگی به خطاهایی که ممکن است در طول عملیات fetch
رخ دهند را نشان می دهد. بلوک try...catch
هر گونه خطا را می گیرد و آنها را در کنسول ثبت می کند. همچنین می توانید خطا را دوباره پرتاب کنید تا توسط مصرف کننده مولد گرفته شود، یا یک شیء خطا را تولید کنید.
مزایای استفاده از توابع مولد ناهمگام
- بهبود خوانایی کد: توابع مولد ناهمگام کد تکرار ناهمگام را در مقایسه با رویکردهای سنتی مبتنی بر Promise خواناتر و قابل نگهداری تر می کنند.
- ساده سازی جریان کنترل ناهمگام: آنها یک روش طبیعی تر و متوالی تر برای بیان منطق ناهمگام ارائه می دهند و استدلال در مورد آن را آسان تر می کنند.
- مدیریت کارآمد منابع: آنها به شما این امکان را می دهند که داده ها را در تکه ها یا جریان ها پردازش کنید، مصرف حافظه را کاهش دهید و عملکرد را بهبود بخشید، به خصوص هنگام کار با مجموعه داده های بزرگ یا جریان های داده در زمان واقعی.
- جداسازی واضح وظایف: آنها منطق تولید داده را از منطق مصرف داده جدا می کنند و باعث ترویج مدولار بودن و قابلیت استفاده مجدد می شوند.
مقایسه با سایر رویکردهای ناهمگام
مولدهای ناهمگام در مقابل Promises
در حالی که Promises برای عملیات ناهمگام اساسی هستند، برای رسیدگی به توالی های مقادیر ناهمگام کمتر مناسب هستند. مولدهای ناهمگام روشی ساخت یافته تر و کارآمدتر برای تکرار بر روی جریان های داده ناهمگام ارائه می دهند.
مولدهای ناهمگام در مقابل RxJS Observables
RxJS Observables یکی دیگر از ابزارهای قدرتمند برای رسیدگی به جریان های داده ناهمگام است. Observables ویژگی های پیشرفته تری مانند عملگرها برای تبدیل، فیلتر کردن و ترکیب جریان های داده ارائه می دهند. با این حال، مولدهای ناهمگام اغلب برای سناریوهای تکرار ناهمگام اساسی ساده تر هستند.
سازگاری با مرورگر و Node.js
توابع مولد ناهمگام به طور گسترده در مرورگرهای مدرن و Node.js پشتیبانی می شوند. آنها در تمام مرورگرهای اصلی که از ES2018 (ECMAScript 2018) و Node.js نسخه 10 و بالاتر پشتیبانی می کنند در دسترس هستند.
اگر نیاز به پشتیبانی از محیط های قدیمی تر دارید، می توانید از ابزارهایی مانند Babel برای تبدیل کد خود به نسخه های قدیمی تر جاوا اسکریپت استفاده کنید.
نتیجه گیری
توابع مولد ناهمگام یک افزودنی ارزشمند به مجموعه ابزارهای برنامه نویسی ناهمگام جاوا اسکریپت هستند. آنها یک روش قدرتمند و ظریف برای رسیدگی به تکرار ناهمگام ارائه می دهند و پردازش جریان های داده را به طور کارآمد و قابل نگهداری آسان تر می کنند. با درک پروتکل های تکرار ناهمگام و نحوه نوشتن توابع مولد ناهمگام، می توانید از مزایای آنها در طیف گسترده ای از برنامه ها، از جریان داده از API ها گرفته تا پردازش فایل های بزرگ و رسیدگی به جریان های داده در زمان واقعی، استفاده کنید.
یادگیری بیشتر
- MDN Web Docs: AsyncGeneratorFunction
- Exploring ES2018: Asynchronous Iteration
- Node.js Documentation: Consult the official Node.js documentation for streams and file system operations.