Generators در حقیقت نوعی Iterator هستند. آنها نوع خاصی از توابع هستند که قابلیت تعلیق و از سرگیری مجدد را دارند. برای رسیدن به این هدف، اینبار تعریف function به صورت *function خواهد بود و در آن برای بازگشت مقادیر، از واژهی کلیدی yield استفاده میشود.
یک نمونه مثال ابتدایی از Generators را در کدهای زیر مشاهده میکنید:
این متد خاص با یک ستاره پس از نام function مشخص شدهاست و همچنین برای بازگشت مقادیر از واژهی کلیدی yield استفاده میکند. روش فراخوانی دستی آن نیز به صورت زیر است:
همانطور که مشاهده میکنید، همانند Iterators، هر بار که متد next آنها فراخوانی میشود، شیءایی را با خواص value و done بازگشت میدهند. هر زمانیکه done مساوی true شد، یعنی کار آن به پایان رسیدهاست.
و یا میتوان بجای فراخوانی دستی متد next، از حلقهی جدید for of نیز برای کار با آنها استفاده کرد:
امکان ترکیب Generators نیز وجود دارد:
در اینجا یک متد Generator به نام random1_20، به تعداد نامتناهی اعداد اتفاقی بین 1 تا 20 را بازگشت میدهد و در این بین، yield آن خود نیز یک Generator دیگر است.
فقط در این حالت بجای yield معمولی از *yield استفاده میشود. از *yield برای کار با هر نوع Iterator ایی میتوان استفاده کرد:
در این مثال از yield معمولی برای بازگشت اعداد و از *yiled برای کار با انواع و اقسام Iterators از آرایهها گرفته تا spread operator، استفاده شدهاست.
کاهش مصرف حافظهی برنامه با استفاده از Generators
در مثال زیر، قرار است لیستی از rows بازگشت داده شود. در اینجا یک آرایه تشکیل شده و هربار اطلاعاتی به آن push میشود و در نهایت این آرایه بازگشت داده خواهد شد:
اما با استفاده از Generators دیگر نیازی نیست تا یک آرایه برای جمع آوری این لیست تشکیل شود و به این ترتیب مصرف حافظهی برنامه کاهش خواهد یافت و همچنین اینبار این خروجی میتواند نامتنهاهی باشد (کاری که با استفاده از آرایههای معمولی قابل انجام نیست):
روشهایی برای خاموش کردن Generators
Generators علاوه بر متد next، دارای متدهای return و throw نیز هستند.
فراخوانی (generator.throw(error همانند این است که در بین کار، به متدی برخوردهایم که استثنایی را صادر کردهاست و سبب خاتمهی کار Generator شدهاست.
اگر متد return آنها فراخوانی شود، کار Generator پایان مییابد (خاصیت done شیء بازگشتی بلافاصله true میشود) و فراخوانی next، پس از آن، دیگر اثری نخواهد داشت:
مشابه آن حالتی است که در بین yieldها یک return وجود داشته باشد:
در این حالت نیز return سبب خاتمهی Generator شدهاست.
اگر به متد return خود Generator، پارامتری ارسال شود، این مقدار، مقدار نهایی بازگشت داده شده خواهد بود:
در این مثال (g.return(5 سبب خاتمهی Generator شدهاست و همچنین مقدار نهایی آنرا نیز تعیین کردهاست (عدد 5 بجای عدد 2).
در حالتیکه به متد return پارامتری ارسال میشود، قسمت finally بدنهی try/finally نوشته شده حتما اجرا خواهد شد و سپس کار خاتمه پیدا میکند:
در این مثال (g.return(6 پس از yield 2 فراخوانی شدهاست. اما همانطور که مشاهده میکنید، بدنهی finally کاملا اجرا شده و سپس Generator با عدد 6 خاتمه یافتهاست.
یک نمونه مثال ابتدایی از Generators را در کدهای زیر مشاهده میکنید:
function* generator () { yield 1; // pause yield 2; // pause yield 3; // pause yield 'done?'; // done }
let gen = generator(); // [object Generator] console.log(gen.next()); // Object {value: 1, done: false} console.log(gen.next()); // Object {value: 2, done: false} console.log(gen.next()); // Object {value: 3, done: false} console.log(gen.next()); // Object {value: 'done?', done: false} console.log(gen.next()); // Object {value: undefined, done: true} console.log(gen.next()); // Object {value: undefined, done: true}
و یا میتوان بجای فراخوانی دستی متد next، از حلقهی جدید for of نیز برای کار با آنها استفاده کرد:
for (let val of generator()) { console.log(val); // 1 // 2 // 3 // 'done?' }
function* random (max) { yield Math.floor(Math.random() * max) + 1; } function* random1_20 () { while (true) { yield* random(20); } } let rand = random1_20(); console.log(rand.next()); console.log(rand.next());
فقط در این حالت بجای yield معمولی از *yield استفاده میشود. از *yield برای کار با هر نوع Iterator ایی میتوان استفاده کرد:
function* multiplier (value) { yield value * 2; yield value * 3; yield value * 4; yield value * 5; } function* trailmix () { yield 0; yield* [1, 2]; yield* [...multiplier(2)]; yield* multiplier(3); }
کاهش مصرف حافظهی برنامه با استفاده از Generators
در مثال زیر، قرار است لیستی از rows بازگشت داده شود. در اینجا یک آرایه تشکیل شده و هربار اطلاعاتی به آن push میشود و در نهایت این آرایه بازگشت داده خواهد شد:
function splitIntoRows(icons, rowLength) { var rows = []; for (var i = 0; i < icons.length; i += rowLength) { rows.push(icons.slice(i, i + rowLength)); } return rows; }
function* splitIntoRows(icons, rowLength) { for (var i = 0; i < icons.length; i += rowLength) { yield icons.slice(i, i + rowLength); } }
روشهایی برای خاموش کردن Generators
Generators علاوه بر متد next، دارای متدهای return و throw نیز هستند.
فراخوانی (generator.throw(error همانند این است که در بین کار، به متدی برخوردهایم که استثنایی را صادر کردهاست و سبب خاتمهی کار Generator شدهاست.
اگر متد return آنها فراخوانی شود، کار Generator پایان مییابد (خاصیت done شیء بازگشتی بلافاصله true میشود) و فراخوانی next، پس از آن، دیگر اثری نخواهد داشت:
function* numbers () { yield 1 yield 2 yield 3 } var g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.return()) // <- { done: true } console.log(g.next()) // <- { done: true }, as we know
function* numbers () { yield 1 yield 2 return 3 yield 4 } console.log([...numbers()]) // <- [1, 2]
اگر به متد return خود Generator، پارامتری ارسال شود، این مقدار، مقدار نهایی بازگشت داده شده خواهد بود:
function* numbers () { yield 1 yield 2 return 3 yield 4 } var g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.return(5)) // <- { done: true, value: 5 } console.log(g.next()) // <- { done: true }
در حالتیکه به متد return پارامتری ارسال میشود، قسمت finally بدنهی try/finally نوشته شده حتما اجرا خواهد شد و سپس کار خاتمه پیدا میکند:
function* numbers () { yield 1 try { yield 2 } finally { yield 3 yield 4 } yield 5 } var g = numbers() console.log(g.next()) // <- { done: false, value: 1 } console.log(g.next()) // <- { done: false, value: 2 } console.log(g.return(6)) // <- { done: false, value: 3 } console.log(g.next()) // <- { done: false, value: 4 } console.log(g.next()) // <- { done: true, value: 6 }