تعداد بازدید: 158

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

ld-in object) که معمولا در بدنه آن ها یک مولد با چرخه بی نهایت وجود دارد و هر زمان که نیاز باشد با یک ارجاع پردازش این مولد را باز و اجرا و مجددا می بندیم.

Generator ها در جاوا اسکریپت به صورت عادی با Constructor قابل ساختن نیستند و باید با استفاده از توابع * آن ها را بسازیم . function * ( تابعی که با کاراکتر * همراه است) یک خروجی Generator Object تولید می کند که به آن Generator Function می گوییم.

با فراخوانی این گونه توابع بدنه تابع به صورت عادی اجرا نمی شود و یک شئ برای مدیریت اجرای پردازش های این تابع بر می گردد. این گونه توابع به علت مدیریت شخصی پردازش درون آن ها به صورت ناهمگام اجرا می شوند.نحوه عملکرد این گونه توابع به این شکل است که می توان از آن ها خارج شد و دوباره وارد شد و variable scope ها را مجددا رجیستر کرد.

function* name(param0) {
  statements
}

بدنه توابع مولد یا Generator’s Function’s Body از چندین گام متصل به هم معمولا تشکیل شده است که به هر یک از آن ها یک گام معلق (current suspended position) از بدنه تابع می گوییم و با yield تعیین می کنیم.

function * generator(){
   yield 'a'
   yield 'b'
   yield 'c'
}

هر یک از قسمت های بالا که با yield مشخص شده است یک گام معلق (suspended position) است. این گام ها توسط شئ بازگشتی از این تابع قابل دسترس است .شئ برگشتی از این توابع با استفاده از متد next می تواند هر یک از این گام ها را اجرا کند.

let gen = generator()

console.log(gen.next()) //{value: 'a',done: false}
console.log(gen.next()) //{value: 'b',done: false}
console.log(gen.next()) //{value: 'c',done: false}
console.log(gen.next()) //{value: undefined,done: true}

همان طور که در قطعه کد بالا مشاهده می کنید به ازای اجرای هر گام از این شئ Generator یک شئ برمی گردد که یک مقدار آن value و یک مقدار دیگر done است . مقدار value مقداری است که در هر گام از بدنه تابع به برگشت داده می شود و مقدار done نتیجه کل پردازش است که در مرحله آخر برابر true می شود. در مثال زیر با استفاده از این ویژگی ها یک مثال برای چاپ نام استفاده کردیم

function * whoAmI(){
  yield 'A'
  yield 'b'
  yield 'o'
  yield 'l'
  yield 'f'
  yield 'a'
  yield 'z'
  yield 'l'
}

var name=''
const gen = whoAmI()
do{
  var yield = gen.next()
  name = (undefined != yield.value) ? name + yield.value : name
} while(!yield.done)

console.log(name)

نکته : برای تعریف Generator ها از Arrow functions استفاده نکنید.

ماهیت Generators بسیار شبیه به مولد ها برق در دنیای فیزیکی هستند. به این شکل که هر دو در گام های متفاوت یک پالس تولید می کنند و هر دو قابلیت اجرای یک چرخه را دارند. در هر گام وضعیت Generator دو حالت دارد . حالت اول done:false است و حالت دوم done:true است که در صورتی که به این حالت برسیم پایان پردازش را خواهیم داشت و از این پس بقیه گام ها نتایج یکسان خواهند داشت.

نکته : ترکیب Generators , Promise برای مدیریت پردازش های ناهمگام می باشد.

Generator Instance Methods

هر پردازش در بدنه تابع Generator از سه حالت خارج نمی باشد . یا در حال اجرای گام های پردازش است (try) یا با خطا برخورد می کند (catch) و یا به انتها می رسد (finally) و همان نقطه ای که در توابع عادی return می کردیم. به ازای این سه حالت ما سه متد برای مدیریت این حالت ها بیرون از بدنه تابع از طریق شئ Generator خواهیم داشت.

  • Generator.prototype.next() : این متد برای حرکت یا گام بعدی کاربرد دارد. ورودی در این متد بسته به آن گام جاری معلق در بدنه ممکن است از سوی شما تعیین یا از بدنه برگشت داده شود.
  • Generator.prototype.return() : این متد برای اتمام پردازش گام ها استفاده می شود . خروجی value در حقیقت همان مقدار return تابع است.
  • Generator.prototype.throw() : کاربرد آن برای اجرای مرحله catch در بدنه تابع generator می باشد.

Generator.prototype.next

این متد می تواند یک ورودی (اختیاری) داشته باشد و خروجی آن با فرمت همیشگی گام های Generator باشند.

Params : همان مقدار ورودی که جای کلمه yield در بدنه تابع می نشیند.

Return Value : یک شئ با فرمت زیر :

  • done :
    • true : به پایان این پردازش رسیدیم و در نقطه return تابع هستیم.
    • false : در بدنه هستیم و گام ها هنوز تمام نشده است.
  • value : مقدار برگشت داده شده از بدنه تابع است . اگر مقداری برگشت داده نشده باشد برابر undefined است .

Example name list

در مثال بالا از یک generator برای تولید index استفاده می کنیم و یک لیست با یک نام می سازیم که در هر مرحله از next همان نام را به صورت ترکیبی با index بر می گرداند.

function * generateCounter(){
  for(let i=0;i<10;i++){
    yield i
  }
}

function * generateStudentList(name,counterGen){
  var counter = counterGen.next().value
  yield `${counter}-${name}`
  var counter = counterGen.next().value
  yield `${counter}-${name}`
  var counter = counterGen.next().value
  yield `${counter}-${name}`
}

let counterGen = generateCounter()
let studentListGen = generateStudentList('Abolfazl',counterGen)
console.log(studentListGen.next().value)
console.log(studentListGen.next().value)
console.log(studentListGen.next().value)
/*
0-Abolfazl debugger eval code:18:9
1-Abolfazl debugger eval code:19:9
2-Abolfazl debugger eval code:20:9
*/

Example Loop throw generator

در مثال زیر تمامی مراحل موجود در بدنه generator را در یک حلقه پیمایش می کنیم.

function * loopThrow(count) {
   for(let i=0;i<count;i++){
      yield i
   }
}

for(let counter of loopThrow(10)){
   console.log(counter)
}

Endless loop (infinity Iterator)

حلقه بی نهایت statement است که با Generator همسو است . یعنی با generator می توان گام های یک پردازش را داشت و همچنین در حلقه بی نهایت می توان تا بی نهایت گام تولید کرد . پس مدیریت پردازش در generator مشکل بی نهایت شدن حلقه را حل می کند.

برای ساخت یک حلقه بی نهایت می توان از while(true) یا for بدون شرط پایان یعنی به شکل for(i=0; ;i++) اقدام کرد.

for(let i=0;i<10;i++){
  yield i
}
let index=0
while(true){
  yield index++
}

مثال لیست نام را در با الگوریتم حلقه بی نهایت اجرا کنید تا نتیجه را ببینید. در مثال بدون حلقه بی نهایت تا index شماره ۱۰ قابلیت اجرا داشتیم و این مشکل با Endless loop قابل حل است. در مثال زیر ۱۰ مرحله از یک generator را اجرا می کنیم

function * generatorNumber(start){
   for(let i=start; ;i++){
      yield i
   }
}

const numGen = generatorNumber(10)
for(let i=0;i<10;i++){
   console.log(numGen.next().value)
}

yield *

در این expression ما می توانیم یک generator دیگر را در گام های تابع generator خودمان اجرا کنیم. این اجرا به این شکل است که تا گام انتهایی تابع ثانی (other generator) و رسیدن به حالت done:true گام بعدی تابع خودمان را نداریم. در مثال زیر 0 تا 5 را در این پردازش اجرا می کنیم.

function * generatorPlus(p){
   for(i=1;i<5;i++){
      yield p+i
   }
}

function * generator(i){
   yield i
   yield * generatorPlus(i)
   yield 5
}

const counterGen = generator(0)
console.log(counterGen.next().value)//0
console.log(counterGen.next().value)//1
console.log(counterGen.next().value)//2
console.log(counterGen.next().value)//3
console.log(counterGen.next().value)//4
console.log(counterGen.next().value)//5
console.log(counterGen.next().value)//undefined

Example Passing Argument

در مثال زیر در حین استفاده از next به آن آرگومان هم پاس می دهیم و می توانیم آن را در بدنه تابع استفاده کنیم.

function * logGenerator(){
   for(let i=0;i<10;i++){
      console.log(i,yield)
   }
}

const logGen = logGenerator()
logGen.next() //must runed until first yield
logGen.next('reza') //0 'reza'
logGen.next('fateme') //1 'fateme'
logGen.next('abolfazl') //2 'abolfazl'
logGen.next('zahra') //3 'zahra'

Example return statement

در مثال زیر ما می توانیم در مرحله done:true با استفاده از کلمه کلیدی return یک مقدار را در بدنه generator برگردانیم. این return می تواند در بلوک finally هم باشد که اگر در این بلوک باشد تا اجرا نشدن تمامی گام ها done برابر false است . مثال زیر (به صورت نمادین) مراحل یک بازی را اجرا می کند که هر گام یک مرحله از بازی است.

function * gameGenerator(name){
   yield  `welcome to our game ${name}`
   yield 'level one'
   yield 'level tow'
   yield 'level three'
   return `you win ${name}`
   /* after this section is unreachable beacuse of return */
}

const gameGen = gameGenerator('Abolfazl')
console.log(gameGen.next().value)//welcome to our game Abolfazl
console.log(gameGen.next().value)//level one
console.log(gameGen.next().value)//level tow
console.log(gameGen.next().value)//level three
console.log(gameGen.next().value)//you win Abolfazl

Example generator as Object property

تابع generator می تواند به صورت یک ویژگی از یک شئ یا یک متد از یک کلاس باشد. دقت داشته باشید در زمان تعریف متد یا ویژگی * در ابتدای نام است.

const obj = {
   //or define as *generator(){}
   generator: function * (){
      yield 1
      yield 2
   }
}
const obj1 = obj.generator()
console.log(obj1.next().value)
console.log(obj1.next().value)
class Person{
   *step(){
      for(let i=0;i<5;i++){
         yield i
      }
      return 'you arrive'
   }
}

const ali = new Person
const aliGen = ali.step()
console.log(aliGen.next().value)
console.log(aliGen.next().value)
console.log(aliGen.next().value)
console.log(aliGen.next().value)
console.log(aliGen.next().value)
console.log(aliGen.next().value)
console.log(aliGen.next().value)

Generator.prototype.return

در این متد ما برای مدیریت return در بدنه تابع generator می توانیم از این متد استفاده کنیم.

function * gen(){
   yield 1
   yield 2
   yield 3
}

const g = gen()
console.log(g.next())
console.log(g.next())
console.log(g.return('end'))

اگر روند اجرای پردازش به گونه ای باشد که لازم بدانیم تمامی مراحل به صورت کامل اجرا شوند لازم است که از بلوک try...finally استفاده کنیم. در این صورت اگر پیش اجرای تمامی yield ها return کنیم done برابر false خواهد بود.

function * gen(){
   try{
      yield 1
      yield 2
      yield 3
   }finally{
      yield 'finally'
   }
}

const g = gen()
console.log(g.next())
console.log(g.next())
console.log(g.return('early return'))

خروجی اجرای متدهای شئ generator در مثال بالا به شکل زیر خواهد بود:

Object { value: 1, done: false }
Object { value: 2, done: false }
Object { value: "finally", done: false }

Generator.prototype.throw

این متد برای مدیریت بخش catch در بدنه تابع generator می باشد. در این مرحله می توان بلوک catch را در این بدنه اجرا کرد. در صورتی که بدنه شامل بلوک try…catch نباشد خطا خواهیم داشت.

function * gen(){
   let index = 0
   while (true) {
      try{
         yield index++
      } catch (e) {
         console.log(e)
      } 
   }
}

const g = gen()

console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.throw(new Error('Error occured')))
console.log(g.next())

کاربرد اصلی generator

در استفاده از generators این موضوع مطرح است که چه زمانی از آن استفاده کنیم. generator ها معمولا در بدنه پردازش های ما با حلقه های بی نهایت همراه است. به این شکل که اگر قرار شد یک سری عملیات مرتبط با هم را به صورت بی نهایت (معمولا گام پایان معلوم نیست) استفاده کنیم از generator ها همراه endless loop استفاده می کنیم. به این ترتیب می توان یک پردازش بی نهایت را داشته باشیم.

function * powerGenerator(n){
   //endless loop
   for(let number = n; ;number *= n){
      yield number
   }
}

const powerGen = powerGenerator(2)

console.log(powerGen.next().value)
console.log(powerGen.next().value)
console.log(powerGen.next().value)
console.log(powerGen.next().value)
console.log(powerGen.next().value)
console.log(powerGen.next().value)
console.log(powerGen.next().value)
console.log(powerGen.next().value)

در مثال بالا ما توان مضرب ۲ را تا هر چند مرحله که خواستیم می توانیم بدست بیاوریم . دقت داشته باشید که خروجی مرحله بعد از ورودی مرحله قبل بدست می آید و در این ترکیب مراحل به هم پیوسته است . در صورتی که ما قرار بود این رابطه یا پیوستگی را نداشتیم یک تابع را چند بار اجرا می کردیم. حال می توانیم این generator را در جای جای برنامه استفاده کنیم . در یک جا مثلا ۵ گام در یک جای دیگر ۱۰ گام و در یک جای دیگر ۲۰ گام . پیش از کامل شدن این برنامه حداکثر گام اجرایی برای این generator معلوم نبود و این حالت می تواند بسیار کاربردی باشد (ترکیب endless loop و generator)

مطالب مشابه

اشتراک گذاری :

مدیر وب سایت گنوتک . برنامه نویسی رو با زبان C در هفده سالگی شروع کردم . در حال حاضر به برنامه نویسی php برپایه معماری MVC , HMVC و همچنین سیستم مدیریت محتوای WordPress و فریم ورک محبوب لاراول علاقه مند هستم و دوست دارم اطلاعاتم رو با شما به اشتراک بگذارم.

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

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *