با اینکه در جاوااسکریپت از function generator زیاد استفاده نمیکنیم ولی ممکن است زمانی استفاده از آن ضروری شود.
سازنده تابع یا function generator در es6 معرفی شد. برای درک function generator بهتر است. یکی ایجاد و از آن استفاده کنیم :
function* counterGenerator(){ // your code }
برای تعریف function generator باید بعد از کلمه کلیدی function یک کاراکتر * قرار دهیم تا مشخص شود میخواهیم یک function generator ایجاد کنیم و نه یک تابع معمولی!
بهتر است ابتدا یک مثال بزنیم :
function counterGenerator(){ return 1; } const counter = ounterGenerator(); console.log(counter); // output : 1
انتظار داریم با اجرای این کد، تابع counterGenerator مقدار 1 را به ما تحویل دهد، انتظار بجایی است اما برای یک function generator چطور؟
function* counterGenerator(){ return 1; } const counter = ounterGenerator(); console.log(counter); // output : ounterGenerator object
اکنون با اجرای این دستور counterGenerator به ما یک object تحویل میدهد. برای استفاده از این object باید از یک متد درون آن بنام next استفاده کنیم. برای مثال :
function* counterGenerator(){ return 1; } const counter = ounterGenerator(); const result = counter.next(); console.log(result); // { value: 1, done: true }
خروجی متد next یک object است. که پراپرتی value مقدار برگشتی از تابع و done وضعیت اجرای تابع را نمایش میدهد. برای درک هدف از استفاده از function generator بهتر است با کلمه کلیدی yield آشنا شویم. yield عملکردی تقریبا مشابه return دارد، اما با اندکی تفاوت. به کد زیر دقت کنید :
function* counterGenerator() { yield 1; yield 2; yield 3; yield 4; } const counter = counterGenerator(); console.log(counter.next()); // output : {value: 1, done: false}
اکنون با اجرای next مقدار 1 را برگشت میدهد و done هم false است.
console.log(counter.next()); // output : {value: 2, done: false}
با اجرای مجدد next مقدار 2 را برگشت میدهد و done همچنان false است.
console.log(counter.next()); // output : {value: 3, done: false}
با اجرای سوم next مقداری که برای yield سوم تعیین کردیم، یعنی 3 را برگشت میدهد، ضمنا همچنان done مقدار false را دارد.
console.log(counter.next()); // output : {value: 4, done: false}
با اجرای چهارم next مقدار yield چهارمی را برگشت میدهد، یعنی مقدار چهارم برگشت داده میشود ، دقت کنید همچنان done مقدار fale دارد.
console.log(counter.next()); // output : {value: undefined, done: true} console.log(counter.next()); // output : {value: undefined, done: true} console.log(counter.next()); // output : {value: undefined, done: true}
اکنون که همه yield ها برگشت داده شدند مقدار done به true تغییر کرد یعنی دیگر کار تابع سازنده ما به اتمام رسیده.
function* counterGenerator() { yield 1; yield 2; return 3; yield 4; } const counter = counterGenerator(); console.log(counter.next()); // output : {value: 1, done: false} console.log(counter.next()); // output : {value: 2, done: false} console.log(counter.next()); // output : {value: 3, done: true} console.log(counter.next()); // output : {value: undefined, done: true} console.log(counter.next()); // output : {value: undefined, done: true}
همینطور که مشاهده میکنید با دفعه اول و دوم اجرای next مقدار خروجی yield هم به ترتیب 1 و 2 میشود. اما با اجرای سوم مقدار 3 برگشت داده میشود و done هم true میشود، وقتی done برابر با true قرار گرفت دیگر همواره value برابر با undefined میشود یعنی تابع ما چیز دیگری برای برگشت دادن ندارد.
متد next یک object با دو پراپرتی برگشت میدهد، پراپرتی value مقدار برگشتی از yield را و پراپرتی done هم به ما میگوید که کار تابع سازنده ما تمام شده یا خیر!
با استفاده تابع سازنده قادرخواهیم بود فرایندها را ترتیب گذاری کنیم. همچنین از توابع سازنده درون حلقه نیز میتوانیم استفاده کنیم :
function* counterGenerator() { yield 1; yield 2; yield 3; yield 4; } const counter = counterGenerator(); for (var i=1;i<=5;i++){ console.log( counter.next() ); } /* output : { value: 1, done: false } { value: 2, done: false } { value: 3, done: false } { value: 4, done: false } { value: undefined, done: true } */
یا درون حلقه for ... of :
function* counterGenerator() { yield 1; yield 2; yield 3; yield 4; } const counter = counterGenerator(); for (count of counter){ console.log( count ); } /* output : 1 2 3 4 */
به کد زیر دقت کنید :
function* counterGenerator(){ let count = 1; while (true) { yield count++; } } const counter = counterGenerator(); for (var i=0; i<10; i++){ console.log( counter.next().value ); } /* 1 2 3 4 5 6 7 8 9 10 */
با هر بار فراخوانی next مقدار count برگشتی از yield هم یکی افزایش میابد.
const counter = counterGenerator(); setInterval(()=> console.log(counter.next().value); },1000);
با استفاده از setInterval میتوان یک ثانیه شمار ایجاد کرد. اینکه منطقی باشد یا خیر، بحث دیگری است...
پس دانستیم که با تابع سازنده میتوان فرایندهای مختلف را ترتیب گذاری کرد سپس برای اینکه فرایند اجرای دستورات را برای لحظاتی متوقف کرد و سپس آنرا ادامه داد از آن استفاده کنیم.
برای مثال وقتی از درون ریداکس یک اکشن را dispatch میکنیم :
store.dispatch({type:ACTION});
تابع سازنده میتواند در سطح middleware تا نهایی شدن اکشن، ارسال به reducer را متوقف کند. پس از آماده شدن اکشن دستور next را اجرا و ادامه مراحل ارسال اکشن به reducer اجرا می شود.