بررسی Generator در جاوااسکریپت

Generator در جاوااسکریپت این امکان را می‌دهد که به راحتی iteratorها را تعریف کنیم و کدی بنویسیم که بتوان آن را متوقف و در ادامه اجرا کرد. این قابلیت کنترل دقیق‌تری بر جریان اجرای کد فراهم می‌کند.

بسیاری از توسعه‌دهندگان برای مدیریت تسک‌های asynchronous به ابزارهایی مانند RxJS یا سایر observableها روی می‌آورند، اما generatorها اغلب نادیده گرفته می‌شوند، در حالی که می‌توانند بسیار قدرتمند باشند.

در این مقاله بررسی می‌کنیم که چگونه generatorها به ما اجازه می‌دهند از یک تابع خارج شویم، state را مدیریت کنیم، پیشرفت عملیات‌های طولانی را گزارش دهیم و در نهایت کدی خواناتر داشته باشیم. همچنین، آن‌ها را با راه‌حل‌های محبوبی مانند RxJS مقایسه می‌کنیم تا ببینیم generatorها در چه مواردی بهتر عمل می‌کنند و چه زمانی استفاده از روش‌های دیگر منطقی‌تر است.

درک Generator در جاوااسکریپت

به طور ساده، generator یک نوع خاص از تابع در جاوااسکریپت است که می‌تواند در حین اجرا متوقف و بعداً ادامه داده شود. این ویژگی به ما اجازه می‌دهد جریان اجرای کد را دقیق‌تر از توابع عادی کنترل کنیم.

یک تابع generator در جاوااسکریپت یک آبجکت generator برمی‌گرداند که با پروتکل‌های iterable و iterator سازگار است.

Generatorها برای اولین بار در ES6 معرفی شدند و از آن زمان به یکی از قابلیت‌های مهم جاوااسکریپت تبدیل شده‌اند. برای تعریف آن‌ها از کلمه‌ی کلیدی

function
function همراه با
*
* استفاده می‌شود، به این شکل:
function*
function*.

در ادامه، یک مثال از نحوه استفاده از generatorها را داریم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function* generatorFunction() {
return "Hello World"; //generator body
}
function* generatorFunction() { return "Hello World"; //generator body }
function* generatorFunction() {
  return "Hello World"; //generator body
}

گاهی اوقات ممکن است

*
* را قبل از نام تابع ببینیم، مانند
function*
function*. در حالی که این سینتکس کم‌تر رایج است، اما همچنان معتبر می‌باشد.

تفاوت Generatorها با توابع عادی

در نگاه اول، یک generator در جاوااسکریپت ممکن است شبیه یک تابع عادی به نظر برسد (به جز

*
*)، اما تفاوت‌های مهمی وجود دارد که آن‌ها را منحصربه‌فرد و قدرتمند می‌کند.

در یک تابع عادی، زمانی که آن را فراخوانی می‌کنیم، از ابتدا تا انتها اجرا می‌شود و هیچ راهی برای توقف در میانه و ادامه آن وجود ندارد. اما generatorها این امکان را می‌دهند که اجرای کد را در هر نقطه‌ای که

yield
yield قرار دارد متوقف کنیم و بعداً آن را ادامه دهیم.

این ویژگی توقف‌پذیری، باعث می‌شود که state بین توقف‌های مختلف حفظ شود، که این قابلیت را برای پردازش مجموعه داده‌های بزرگ در بخش‌های کوچک ایده‌آل می‌کند. علاوه بر این، در حالی که یک تابع عادی هنگام اجرا مقدار نهایی خود را return می‌کند، generatorها هنگام فراخوانی یک آبجکت generator برمی‌گردانند. این آبجکت یک iterator است که می‌توان از آن برای پیمایش در دنباله‌ای از مقادیر استفاده کرد.

هنگامی که با یک generator کار می‌کنیم، آن را فقط یک‌بار فراخوانی نمی‌کنیم و تمام! بلکه با استفاده از متدهایی مانند

next()
next()،
throw()
throw() و
return()
return() می‌توانیم state آن را از بیرون کنترل نماییم:

  • next(value)
    next(value): اجرای generator را ادامه می‌دهد. همچنین، می‌تواند یک مقدار را به داخل generator ارسال کند که این مقدار توسط آخرین
    yield
    yield دریافت می‌شود. این متد یک آبجکت شامل دو ویژگی
    value
    value و
    done
    done (که مشخص می‌کند iterator به پایان رسیده است یا نه) را برمی‌گرداند.
  • throw(error)
    throw(error): یک خطا را در داخل throw generator می‌کند، که به ما امکان مدیریت استثناها را می‌دهد.
  • return(value)
    return(value): اجرای generator را زودتر از موعد خاتمه داده و مقدار مشخص شده را برمی‌گرداند.

این ارتباط دوطرفه یک برتری بزرگ نسبت به توابع عادی محسوب می‌شود و می‌تواند برای مدیریت پردازش‌های پیچیده، جریان‌های کاری و کنترل خطاها مفید باشد.

مثال استفاده از Generatorها

برای شروع، تابع generator

Hello World
Hello World را که قبلاً نشان دادیم، مقداردهی اولیه کرده و value آن را دریافت می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const generator = generatorFunction();
const generator = generatorFunction();
const generator = generatorFunction();

وقتی تابع

generatorFunction()
generatorFunction() را فراخوانی کرده و در یک متغیر ذخیره می‌کنیم، بلافاصله رشته
"Hello World"
"Hello World" را دریافت نمی‌کنیم. در عوض، یک آبجکت generator دریافت می‌کنیم که در ابتدا در حالت suspended قرار دارد. این یعنی اجرا متوقف شده و هنوز هیچ کدی اجرا نشده است.

اگر

generator
generator را در کنسول لاگ بگیریم، می‌بینیم که یک مقدار ساده نیست، بلکه یک آبجکت است که نشان می‌دهد generator هنوز فعال می‌باشد. برای دریافت مقدار تابع generator، باید متد
next()
next() را روی این آبجکت فراخوانی کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const result = generator.next();
const result = generator.next();
const result = generator.next();

خروجی حاصل به صورت زیر خواهد بود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{ value: 'Hello World', done: true }
{ value: 'Hello World', done: true }
{ value: 'Hello World', done: true }

این فراخوانی، رشته “Hello World” را به عنوان مقدار کلید value در آبجکت بازگشتی به ما می‌دهد. همچنین، ویژگی

done
done مقدار
true
true خواهد داشت، زیرا دیگر هیچ کدی برای اجرا باقی نمانده است. در نتیجه، وضعیت تابع آبجکت generator از suspended به closed تغییر می‌کند.

تا اینجا، فقط دیدیم که چگونه می‌توانیم یک مقدار واحد را از یک تابع آبجکت generator برگردانیم. اما اگر بخواهیم چند مقدار مختلف را return کنیم، چه کاری باید انجام دهیم؟
اینجاست که عملگر

yield
yield به کمک ما می‌آید.

عملگر
yield
yield

Generator در جاوااسکریپت به ما اجازه می‌دهد اجرای تابع را متوقف کرده و دوباره آن را ادامه دهیم، و این کار با استفاده از کلمه‌ی کلیدی

yield
yield انجام می‌شود.

به عنوان مثال، فرض کنید یک تابع generator به این شکل داریم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function* generatorFunction() {
yield "first value";
yield "second value";
yield "third value";
yield "last value";
}
function* generatorFunction() { yield "first value"; yield "second value"; yield "third value"; yield "last value"; }
function* generatorFunction() {
  yield "first value";
  yield "second value";
  yield "third value";
  yield "last value";
}

هر بار که متد

next()
next() را روی generator فراخوانی می‌کنیم، تابع تا زمانی که به یک عبارت
yield
yield برسد، اجرا می‌شود و سپس متوقف می‌گردد. در این لحظه، تابع generator یک آبجکت شامل دو ویژگی را return می‌کند:

  • value
    value: مقداری که
    yield
    yield return کرده است.
  • done
    done: یک مقدار Boolean که نشان می‌دهد generator به پایان رسیده است یا نه.

تا زمانی که یک

yield
yield دیگر در تابع وجود داشته باشد (یا
return
return اجرا نشده باشد)، مقدار
done
done برابر با
false
false خواهد بود. اما به محض این که generator هیچ
yield
yieldای برای اجرا نداشته باشد، مقدار
done
done به
true
true تغییر می‌کند.

اگر متد

next()
next() را چهار بار روی یک generator که سه
yield
yield دارد، اجرا کنیم، خروجی به این شکل خواهد بود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const generator = generatorFunction();
generator.next(); // { value: 'first value', done: false }
generator.next(); // { value: 'second value', done: false }
generator.next(); // { value: 'third value', done: false }
generator.next(); // { value: 'last value', done: true }
const generator = generatorFunction(); generator.next(); // { value: 'first value', done: false } generator.next(); // { value: 'second value', done: false } generator.next(); // { value: 'third value', done: false } generator.next(); // { value: 'last value', done: true }
const generator = generatorFunction();

generator.next(); // { value: 'first value', done: false }
generator.next(); // { value: 'second value', done: false }
generator.next(); // { value: 'third value', done: false }
generator.next(); // { value: 'last value', done: true }

ارسال مقادیر به Generatorها

نکته جالب این است که

yield
yield فقط برای return کردن
value
value استفاده نمی‌شود؛ بلکه مثل یک مسیر دوطرفه عمل می‌کند و می‌تواند مقادیر را از بیرون دریافت نماید. این یعنی بین generator و کد فراخوانی‌کننده آن، ارتباط دوطرفه برقرار می‌شود.

برای ارسال مقدار به یک تابع generator در جاوااسکریپت، می‌توانیم متد

next()
next() را همراه با یک آرگومان فراخوانی کنیم. به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function* generatorFunction() {
console.log(yield);
console.log(yield);
}
const generator = generatorFunction();
generator.next(); // First call — no yield has been paused yet, so nothing to pass in
generator.next("first input");
generator.next("second input");
function* generatorFunction() { console.log(yield); console.log(yield); } const generator = generatorFunction(); generator.next(); // First call — no yield has been paused yet, so nothing to pass in generator.next("first input"); generator.next("second input");
function* generatorFunction() {
  console.log(yield);
  console.log(yield);
}

const generator = generatorFunction();

generator.next(); // First call — no yield has been paused yet, so nothing to pass in
generator.next("first input");
generator.next("second input");

این کد موارد زیر را به ترتیب ثبت می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
first input
second input
first input second input
first input
second input

اگر دقت کنیم می‌بینیم که در فراخوانی اول

generator.next()
generator.next()، هیچ مقداری چاپ نمی‌شود، دلیل این است که هنوز هیچ
yield
yield معلقی برای دریافت مقدار وجود ندارد.
اما در فراخوانی دوم که شامل
generator.next("first input")
generator.next("first input") است، مقدار
"first input"
"first input" به
yield
yield معلق قبلی ارسال شده و در خروجی چاپ می‌شود. همین الگو برای فراخوانی‌های بعدی نیز دنبال می‌شود.

این دقیقاً روشی است که generator‌ها امکان ارسال و دریافت داده را بین خود و فراخوانی‌کننده فراهم می‌کنند.

پردازش عملیات‌های طولانی و استریم‌های async

async generatorها با معرفی ECMAScript 2017، به جاوااسکریپت اضافه شدند. این نوع خاص از توابع generator، با Promiseها کار می‌کنند.

با کمک async generatorها، دیگر محدود به اجرای کدهای synchronous نیستیم.
اکنون می‌توانیم تسک‌هایی مانند دریافت داده از یک API، خواندن فایل‌ها، یا هر کاری که نیاز به انتظار برای یک Promise دارد را با این روش مدیریت نماییم.

در ادامه مثالی از یک تابع async generator را داریم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
async function* asyncGenerator() {
yield await Promise.resolve("1");
yield await Promise.resolve("2");
yield await Promise.resolve("3");
}
const generator = asyncGenerator();
await generator.next(); // { value: '1', done: false }
await generator.next(); // { value: '2', done: false }
await generator.next(); // { value: '3', done: true }
async function* asyncGenerator() { yield await Promise.resolve("1"); yield await Promise.resolve("2"); yield await Promise.resolve("3"); } const generator = asyncGenerator(); await generator.next(); // { value: '1', done: false } await generator.next(); // { value: '2', done: false } await generator.next(); // { value: '3', done: true }
async function* asyncGenerator() {
  yield await Promise.resolve("1");
  yield await Promise.resolve("2");
  yield await Promise.resolve("3");
}

const generator = asyncGenerator();
await generator.next(); // { value: '1', done: false }
await generator.next(); // { value: '2', done: false }
await generator.next(); // { value: '3', done: true }

تفاوت اصلی این نوع generator این است که باید روی هر فراخوانی

generator.next()
generator.next() از
await
await استفاده کنیم تا مقدار را دریافت نماییم، زیرا همه چیز به شکل asynchronous اجرا می‌شود.

کاربرد واقعی: دریافت داده‌های صفحه‌بندی‌شده از یک API

در این بخش بررسی می‌کنیم که چگونه می‌توانیم از async generatorها برای دریافت داده‌های صفحه‌بندی‌شده از یک API ریموت استفاده کنیم.
این یک سناریوی عالی برای استفاده از async generatorها است، زیرا می‌توانیم منطق تکرار متوالی داده‌ها را در یک تابع واحد قرار دهیم.

در این مثال، از API  رایگان DummyJSON استفاده می‌کنیم تا لیستی از محصولات صفحه‌بندی‌شده را دریافت نماییم.

برای دریافت داده از این API، باید یک درخواست GET به این Endpoint ارسال کنیم. پارامترهای

limit
limit و
skip
skip را تنظیم می‌کنیم تا بتوانیم تعداد نتایج و میزان جابه‌جایی در لیست را مشخص نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
https://dummyjson.com/products?limit=10&skip=0
https://dummyjson.com/products?limit=10&skip=0
https://dummyjson.com/products?limit=10&skip=0

یک نمونه از پاسخ این Endpoint می‌تواند به شکل زیر باشد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
"products": [
{
"id": 1,
"title": "Annibale Colombo Bed",
"price": 1899.99
},
{...},
// ۱۰ items
],
"total": 194,
"skip": 0,
"limit": 10
}
{ "products": [ { "id": 1, "title": "Annibale Colombo Bed", "price": 1899.99 }, {...}, // ۱۰ items ], "total": 194, "skip": 0, "limit": 10 }
{
  "products": [
    {
      "id": 1,
      "title": "Annibale Colombo Bed",
      "price": 1899.99
    },
    {...},
    // ۱۰ items
  ],
  "total": 194,
  "skip": 0,
  "limit": 10
}

برای بارگذاری مجموعه بعدی از محصولات، تنها کاری که باید انجام دهیم این است که مقدار

skip
skip را به اندازه
limit
limit افزایش دهیم تا تمام داده‌ها را دریافت کنیم.

به این ترتیب، می‌توانیم یک تابع generator سفارشی برای دریافت داده‌ها را به صورت زیر پیاده‌سازی نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
async function* fetchProducts(skip = 0, limit = 10) {
let total = 0;
do {
const response = await fetch(
`https://dummyjson.com/products?limit=${limit}&skip=${skip}`,
);
const { products, total: totalProducts } = await response.json();
total = totalProducts;
skip += limit;
yield products;
} while (skip < total);
}
async function* fetchProducts(skip = 0, limit = 10) { let total = 0; do { const response = await fetch( `https://dummyjson.com/products?limit=${limit}&skip=${skip}`, ); const { products, total: totalProducts } = await response.json(); total = totalProducts; skip += limit; yield products; } while (skip < total); }
async function* fetchProducts(skip = 0, limit = 10) {
  let total = 0;

  do {
    const response = await fetch(
      `https://dummyjson.com/products?limit=${limit}&skip=${skip}`,
    );
    const { products, total: totalProducts } = await response.json();

    total = totalProducts;
    skip += limit;
    yield products;
  } while (skip < total);
}

اکنون می‌توانیم با استفاده از حلقه

for await...of
for await...of روی این تابع تکرار کنیم تا همه محصولات را دریافت نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
for await (const products of fetchProducts()) {
for (const product of products) {
console.log(product.title);
}
}
for await (const products of fetchProducts()) { for (const product of products) { console.log(product.title); } }
for await (const products of fetchProducts()) {
  for (const product of products) {
    console.log(product.title);
  }
}

با این روش، تمامی محصولات را یکی پس از دیگری دریافت می‌کنیم تا زمانی که دیگر داده‌ای برای دریافت باقی نماند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Essence Mascara Lash Princess
Eyeshadow Palette with Mirror
Powder Canister
Red Lipstick
Red Nail Polish
... // ۱۵ more items
Essence Mascara Lash Princess Eyeshadow Palette with Mirror Powder Canister Red Lipstick Red Nail Polish ... // ۱۵ more items
Essence Mascara Lash Princess
Eyeshadow Palette with Mirror
Powder Canister
Red Lipstick
Red Nail Polish
... // ۱۵ more items

چرا این روش مفید است؟

  • مدیریت ساده صفحه‌بندی: با قرار دادن کل منطق دریافت داده‌های صفحه‌بندی‌شده در یک async generator، کد ما تمیز و خوانا باقی می‌ماند.
  • دریافت داده‌های بیشتر به صورت خودکار: هر بار که به داده‌های جدید نیاز داریم، generator مجموعه بعدی از نتایج را دریافت کرده و return می‌کند، و این باعث می‌شود فرآیند صفحه‌بندی به یک جریان داده پیوسته تبدیل شود.
  • بهبود عملکرد و کاهش مصرف حافظه: به جای دریافت همه داده‌ها به یک‌باره، می‌توانیم آن‌ها را به تدریج و به اندازه نیاز دریافت نماییم.

در نتیجه، با استفاده از async generatorها، فرآیند دریافت داده‌های صفحه‌بندی‌شده کارآمدتر، خواناتر و ساده‌تر از همیشه خواهد بود.

استفاده از Generatorها به عنوان State Machineها

Generatorها می‌توانند به عنوان State Machineهای ساده عمل کنند، چون به خاطر می‌سپارند که در کجا متوقف شده‌اند.
اما همیشه بهترین گزینه برای مدیریت state نیستند، مخصوصاً وقتی فریم‌ورک‌های مدرن جاوااسکریپت ابزارهای پیشرفته‌ای برای مدیریت state دارند.

چرا استفاده از generatorها به عنوان State Machine همیشه ایده‌آل نیست؟
با این که generatorها حافظه داخلی دارند و می‌توانند فرآیندها را ادامه دهند، اما در بسیاری از موارد، پیاده‌سازی یک State Machine با آن‌ها در جاوااسکریپت پیچیدگی زیادی دارد و ارزش استفاده از روش‌های ساده‌تر را کم می‌کند.

راه حل جایگزین، مدل Actor است. اگر همچنان بخواهیم چنین رویکردی را بررسی کنیم، مدل Actor گزینه بهتری خواهد بود. این مدل که از زبان Erlang نشأت گرفته، بر پایه واحدهای مستقلی کار می‌کند که state و رفتار خود را مدیریت می‌کنند و فقط از طریق ارسال پیام با یکدیگر ارتباط می‌گیرند. این روش باعث ماژولار شدن سیستم و مدیریت بهتر تغییرات state می‌شود.

مقایسه RxJS و Generatorها برای پردازش Web Streamها

وقتی صحبت از پردازش Web Streamها می‌شود، هم generatorها و هم RxJS ابزارهای قدرتمندی هستند، اما هرکدام مزایا و معایب خاص خود را دارند.

خبر خوب این است که می‌توانیم از هر دو آن‌ها در کنار هم استفاده کنیم.

استفاده از Generator برای پردازش Web Streamها

فرض کنید یک API داریم که چندین رشته ۸ کاراکتری تصادفی را به صورت Stream بازمی‌گرداند.
در قدم اول، می‌توانیم یک تابع generator تعریف کنیم که به صورت Lazy، تکه‌های داده را یکی‌یکی return می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Fetch data from HTTP stream
async function* fetchStream() {
const response = await fetch("https://example/api/stream");
const reader = response.body?.getReader();
if (!reader) throw new Error();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
yield value;
}
} catch (error) {
throw error;
} finally {
reader.releaseLock();
}
}
// Fetch data from HTTP stream async function* fetchStream() { const response = await fetch("https://example/api/stream"); const reader = response.body?.getReader(); if (!reader) throw new Error(); try { while (true) { const { done, value } = await reader.read(); if (done) break; yield value; } } catch (error) { throw error; } finally { reader.releaseLock(); } }
// Fetch data from HTTP stream
async function* fetchStream() {
  const response = await fetch("https://example/api/stream");
  const reader = response.body?.getReader();
  if (!reader) throw new Error();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      yield value;
    }
  } catch (error) {
    throw error;
  } finally {
    reader.releaseLock();
  }
}

فراخوانی

fetchStream()
fetchStream() یک async generator بازمی‌گرداند، و سپس می‌توانیم داده‌ها را با یک حلقه پردازش کنیم.

اما RxJS می‌تواند قابلیت‌های بیشتری به این فرآیند اضافه نماید.

استفاده از RxJS برای پردازش Web Streamها

RxJS مجموعه‌ای از عملگرهای قدرتمند مثل:

  • map
    map: برای تبدیل داده‌ها
  • filter
    filter: برای فیلتر کردن داده‌های خاص
  • take
    take: برای محدود کردن تعداد داده‌ها را در اختیار ما قرار می‌دهد.

برای استفاده از این قابلیت‌ها، ابتدا generator را به یک observable تبدیل می‌کنیم.

اکنون از عملگر

take
take برای فیلتر کردن پنج تکه اول داده استفاده خواهیم کرد:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { from, take } from "rxjs";
// Consume HTTP stream using RxJS
async () => {
from(fetchStream())
.pipe(take(5))
.subscribe({
next: (chunk) => {
const decoder = new TextDecoder();
console.log("Chunk:", decoder.decode(chunk));
},
complete: () => {
console.log("Stream complete");
},
});
};
import { from, take } from "rxjs"; // Consume HTTP stream using RxJS async () => { from(fetchStream()) .pipe(take(5)) .subscribe({ next: (chunk) => { const decoder = new TextDecoder(); console.log("Chunk:", decoder.decode(chunk)); }, complete: () => { console.log("Stream complete"); }, }); };
import { from, take } from "rxjs";

// Consume HTTP stream using RxJS
async () => {
  from(fetchStream())
    .pipe(take(5))
    .subscribe({
      next: (chunk) => {
        const decoder = new TextDecoder();
        console.log("Chunk:", decoder.decode(chunk));
      },
      complete: () => {
        console.log("Stream complete");
      },
    });
};

در این مثال، عملگر

from
from در RxJS، تبدیل Generator به یک Observable را انجام می‌دهد که اجازه می‌دهد داده‌ها را به شکل synchronous مدیریت کنیم.

خروجی کد به صورت زیر خواهد بود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
Chunk: ky^p1egh
Chunk: 1q)zIz43
Chunk: xm5aJGSX
Chunk: GSx6a2UQ
Chunk: GFlwWPu^
Stream complete
Chunk: ky^p1egh Chunk: 1q)zIz43 Chunk: xm5aJGSX Chunk: GSx6a2UQ Chunk: GFlwWPu^ Stream complete
Chunk: ky^p1egh
Chunk: 1q)zIz43
Chunk: xm5aJGSX
Chunk: GSx6a2UQ
Chunk: GFlwWPu^
Stream complete

آیا می‌توانیم به جای RxJS از حلقه
for await...of
for await...of
 استفاده کنیم؟

بله، می‌توانیم Stream را به صورت ساده و بدون RxJS مورد استفاده قرار دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Consume the HTTP stream using for-await-of
for await (const chunk of fetchStream()) {
const decoder = new TextDecoder();
console.log("Chunk:", decoder.decode(chunk));
}
// Consume the HTTP stream using for-await-of for await (const chunk of fetchStream()) { const decoder = new TextDecoder(); console.log("Chunk:", decoder.decode(chunk)); }
// Consume the HTTP stream using for-await-of
for await (const chunk of fetchStream()) {
  const decoder = new TextDecoder();
  console.log("Chunk:", decoder.decode(chunk));
}

اما در این روش، مزایای RxJS را از دست می‌دهیم، یعنی نمی‌توانیم از عملگرهایی مثل

take
take برای محدود کردن تعداد داده‌ها استفاده کرد. همچین، مدیریت داده‌های پیچیده دشوارتر خواهد شد.

اما خبر خوبی که وجود دارد این است که ویژگی Iteration Helpers در نسخه بعدی ECMAScript (در حال حاضر در مرحله ۴) اضافه خواهد شد. این قابلیت به ما اجازه می‌دهد که به طور Native خروجی generatorها را فیلتر یا محدود نماییم؛ درست مثل کاری که RxJS برای observableها انجام می‌دهد.

جمع‌بندی

Generator در جاوااسکریپت یک روش قدرتمند، اما کم‌تر شناخته شده برای مدیریت عملیات‌های asynchronous، کنترل state و پردازش جریان‌های داده است. با امکان توقف و ازسرگیری اجرا، این قابلیت کنترل دقیق‌تری نسبت به توابع معمولی فراهم می‌کند، به ویژه زمانی که با تسک‌های طولانی یا مجموعه داده‌های بزرگ سروکار داریم.

با این‌حال، در حالی که generatorها در بسیاری از سناریوها عملکرد خوبی دارند، اما ابزارهایی مانند RxJS یک اکوسیستم قدرتمند از اپراتورها ارائه می‌دهند که مدیریت جریان‌های پیچیده و رویدادمحور را ساده‌تر می‌کنند.

البته نیازی به انتخاب قطعی بین این دو روش نیست؛ بلکه می‌توانیم بسته به نیاز خود، سادگی generatorها را با قدرت تبدیل‌های RxJS ترکیب کرده یا حتی فقط از یک حلقه ساده

for await...of
for await...of در پروژه خود استفاده کنیم.

در آینده، Iteration Helperهای جدید ممکن است امکانات generatorها را به RxJS نزدیک‌تر کنند، اما در آینده‌ای قابل پیش‌بینی، RxJS همچنان گزینه‌ای کلیدی برای مدیریت الگوهای واکنشی پیچیده خواهد بود.

دیدگاه‌ها:

افزودن دیدگاه جدید