λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ’» DEV/Javascript & NodeJS

[Javascript] Generator (μ œλ„€λ ˆμ΄ν„°)

by vodkassi 2021. 10. 26.
728x90

πŸ“ Intro

μ§€λ‚œ κΈ€μ—μ„œ λ‹€λ£¨μ—ˆλ˜ Iterator 와 μΈμ ‘ν•˜κ²Œ μ‚¬μš©λ˜λŠ” κ°œλ… 쀑 ν•˜λ‚˜κ°€ λ°”λ‘œ generator (μ œλ„€λ ˆμ΄ν„°) 이닀. μ œλ„€λ ˆμ΄ν„°λŠ” ES6μ—μ„œ λ„μž…λ˜μ–΄, μ½”λ“œ λΈ”λ‘μ˜ 싀행을 μΌμ‹œ μ€‘μ§€ν–ˆλ‹€κ°€ ν•„μš”ν•œ μ‹œμ μ— μž¬κ°œν•  수 μžˆλŠ” νŠΉμˆ˜ν•œ ν•¨μˆ˜μ΄λ‹€. 이번 κΈ€μ—μ„œλŠ” generator funciton κ³Ό generator의 ν™œμš© 방법을 μ‚΄νŽ΄λ³΄κ³ μž ν•œλ‹€. 

 

 

✨  Generator

MDN μ—μ„œ μ •μ˜ν•˜λŠ” generator λŠ” "generator function (μ œλ„€λ ˆμ΄ν„° ν•¨μˆ˜) λ‘œλΆ€ν„° λ°˜ν™˜λœ 값이며 iterable, iterator protocol 을 μ€€μˆ˜ν•˜λŠ” κ°’" 이닀. μœ„μ˜ μ •μ˜μ—μ„œ ν‘œκΈ°λœ Generator function 의 μ£Όμš” νŠΉμ§•μ€ λ‹€μŒκ³Ό κ°™λ‹€.

 

1. ν•¨μˆ˜ 호좜자 (caller) μ—κ²Œ ν•¨μˆ˜ μ‹€ν–‰μ˜ μ œμ–΄κΆŒμ„ 양도(yield)ν•  수 μžˆλ‹€.

일반 ν•¨μˆ˜λŠ” 호좜 μ‹œ μ œμ–΄κΆŒμ΄ ν•¨μˆ˜μ—κ²Œ λ„˜μ–΄κ°€κΈ° λ•Œλ¬Έμ— μ½”λ“œκ°€ 일괄 μ‹€ν–‰λ˜λ©°, ν˜ΈμΆœμžλŠ” 싀행을 μ œμ–΄ν•  수 μ—†λ‹€. ν•˜μ§€λ§Œ generator function 은 ν˜ΈμΆœμžκ°€ ν•¨μˆ˜ 싀행을 μΌμ‹œ μ€‘μ§€ν•˜κ±°λ‚˜ μž¬κ°œμ‹œν‚¬ 수 μžˆλ‹€. 이λ₯Ό ν•¨μˆ˜μ˜ μ œμ–΄κΆŒμ„ 양도 (yield) ν•œλ‹€κ³  ν‘œν˜„ν•œλ‹€. 

 

2. ν•¨μˆ˜ ν˜ΈμΆœμžμ™€ ν•¨μˆ˜μ˜ μƒνƒœλ₯Ό 주고받을 수 μžˆλ‹€.

일반 ν•¨μˆ˜λŠ” 호좜 μ‹œ λ§€κ°œλ³€μˆ˜λ₯Ό 톡해 μ™ΈλΆ€μ—μ„œ 값을 μ£Όμž…λ°›κ³ , 일괄 μ‹€ν–‰ν•˜μ—¬ 결과값을 λ°˜ν™˜ν•œλ‹€. ν•˜μ§€λ§Œ generator function 은 ν˜ΈμΆœμžμ—κ²Œ μƒνƒœλ₯Ό μ „λ‹¬ν•˜κ³ , 전달받을 수 μžˆλ‹€.

 

3. 호좜 μ‹œ generator λ₯Ό λ°˜ν™˜ν•œλ‹€.

generator ν•¨μˆ˜ 호좜 μ‹œ 일반적인 값을 λ°˜ν™˜ν•˜λŠ” 것이 μ•„λ‹ˆλΌ iterable μ΄λ©΄μ„œ λ™μ‹œμ— iterator 인 generator 객체λ₯Ό λ°˜ν™˜ν•œλ‹€.

 

πŸ’‘  Recap: 볡슡 포인트

Iterable 은 λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ κ°–λŠ”λ‹€.
1. Symbol.iterator λ₯Ό ν”„λ‘œνΌν‹° ν‚€λ‘œ μ‚¬μš©ν•œ λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•œλ‹€
2. Symbol.iterator λ₯Ό ν”„λ‘œν† νƒ€μž… 체인을 톡해 μƒμ†λ°›λŠ”λ‹€ 

Iterator λŠ” λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ κ°–λŠ”λ‹€. 
3. next λ©”μ„œλ“œ μ†Œμœ 
4. next λ©”μ„œλ“œμ˜ κ²°κ³Ό 객체에 done, value ν”„λ‘œνΌν‹°λ₯Ό μ†Œμœ 

λ”°λΌμ„œ generator 객체 μ—­μ‹œ Symbol.iterator λ₯Ό μƒμ†λ°›μœΌλ©° value, done ν”„λ‘œνΌν‹°μ™€ next λ©”μ„œλ“œλ₯Ό μ†Œμœ ν•œλ‹€. ν•˜μ§€λ§Œ μ€‘μš”ν•œ 점은, generator λŠ” iterator μ—λŠ” μ—†λŠ” πŸ“ return, throw λ©”μ„œλ“œλ₯Ό κ°–λŠ”λ‹€λŠ” 것이닀. 

 

μœ„μ˜ μ„Έ 가지 νŠΉμ§•μ„ λ”μš± 잘 μ΄ν•΄ν•˜κΈ° μœ„ν•΄μ„œλŠ” generator function의 μ„ μ–Έ 방법과 ꡬ성을 μ‚΄νŽ΄λ³΄μ•„μ•Ό ν•œλ‹€. μš°μ„ , function* ν‚€μ›Œλ“œλ‘œ ν•¨μˆ˜λ₯Ό μ„ μ–Έν•˜κ³  내뢀에 yield ν‘œν˜„μ‹μ„ ν¬ν•¨ν•˜λ©΄  generator function 이 λœλ‹€. λ©”μ„œλ“œλ‘œ μ„ μ–Έν•  λ•ŒλŠ” * ν‘œμ‹œλ§Œ ν•΄ μ£Όλ©΄ λœλ‹€. 

// Generator functions

function* genFunc() {
	yield 1;
}

const obj = {
	* genFunc() {
    	yield 1;
    }
}

class Test {
	* genFunc() {
    	yield 1;
    }
}

// Check for methods
console.log('next' in genFunc()) // true
console.log('throw' in genFunc()) // true

 

❗ ν™”μ‚΄ν‘œ ν•¨μˆ˜λ‘œλŠ” generator function 을 μ •μ˜ν•  수 μ—†λ‹€.

❗ new μ—°μ‚°μžμ™€ ν•¨κ»˜ μƒμ„±μž ν•¨μˆ˜λ‘œ ν˜ΈμΆœν•  수 μ—†λ‹€. 

 

✨  Generator 의 λ™μž‘ 방식

μ•žμ„œ μ–ΈκΈ‰ν–ˆλ“  generator μ—λŠ” next λ©”μ„œλ“œμ™€ λ”λΆˆμ–΄ κ³ μœ ν•œ yield ν‘œν˜„μ‹κ³Ό throw, return λ©”μ„œλ“œλ₯Ό κ°–λŠ”λ‹€. 예제λ₯Ό ν†΅ν•΄μ„œ μ‰½κ²Œ generator 의 μž‘λ™ 방식을 확인할 수 μžˆλ‹€. 

 

// iterator μ—μ„œλŠ” done 의 쑰건에 λΆ€ν•©ν•˜λŠ” μˆœκ°„ true 둜 μ „ν™˜λœλ‹€. 

const iterFunc = {
    [Symbol.iterator]() {
        let cur = 1;
        const max = 5;
        return {
            next() {
                return {value: cur++, done: cur > max + 1}
            }
        }
    }
}

const test = iterFunc[Symbol.iterator]()
console.log(test.next()) // {value: 1, done: false}
console.log(test.next()) // {value: 2, done: false}
console.log(test.next()) // {value: 3, done: false}
console.log(test.next()) // {value: 4, done: false}
console.log(test.next()) // {value: 5, done: false}
console.log(test.next()) // {value: 6, done: true}
console.log(test.next()) // {value: 7, done: true}
console.log(test.next()) // {value: 8, done: true}



// generator μ—μ„œλŠ” λ§ˆμ§€λ§‰ yield κ°’κΉŒμ§€ λ°˜ν™˜ν•œ 뒀에야 done μƒνƒœλ₯Ό true 둜 μ „ν™˜ν•œλ‹€. 

function* genFunc() {
	yield 1;
    yield 2;
    yield 3;
}

const gen = genFunc();
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next()) // {value: 2, done: false}
console.log(gen.next()) // {value: 3, done: false}
console.log(gen.next()) // {value: undefined, done: true}
console.log(gen.next()) // {value: undefined, done: true}
console.log(gen.next()) // {value: undefined, done: true}



// generator 의 return λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ 전달받은 값을 value κ°’μœΌλ‘œ, true λ₯Ό done κ°’μœΌλ‘œ κ°–λŠ” iterator result object (μ΄ν„°λ ˆμ΄ν„° 리절트 객체) λ₯Ό λ°˜ν™˜ν•œλ‹€.

console.log(gen.return('Ha')) // {value: 'Ha', done: true}



// throw λ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” generator function 이 μ—λŸ¬λ₯Ό μž‘μ•„(catch)μ€˜μ•Ό ν•œλ‹€.
// throw λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ undefined λ₯Ό value κ°’μœΌλ‘œ, true λ₯Ό done κ°’μœΌλ‘œ κ°–λŠ” iterator result object (μ΄ν„°λ ˆμ΄ν„° 리절트 객체) λ₯Ό λ°˜ν™˜ν•œλ‹€.

function* genFunc1() {
    try {
      yield 1;
      yield 2;
      yield 3;
    } catch (e) {
    	console.error(e);
    }
}

const gen1 = genFunc1();
console.log(gen1.next()) // {value: 1, done: false}
console.log(gen1.throw('Error!')) // Error!, {value: undefined, done: true}
console.log(gen1.next()) // {value: undefined, done: true}

 

generator λŠ” μœ„μ˜ μ½”λ“œ μ˜ˆμ œμ—μ„œ λ³Έ 것과 같이 yield ν‚€μ›Œλ“œμ™€ next λ©”μ„œλ“œλ₯Ό 톡해 싀행을 μ€‘μ§€ν–ˆλ‹€κ°€ μž¬κ°œν•  수 μžˆλ‹€. μœ„μ˜ μ˜ˆμ œμ—μ„œ generator function 은 genFunc 와 genFunc1 이며, 이λ₯Ό ν˜ΈμΆœν•˜μ—¬ λ°˜ν™˜λœ generator 객체가 λ°”λ‘œ gen κ³Ό gen1 이닀. λ”°λΌμ„œ next, return λ“±μ˜ λ©”μ„œλ“œλ₯Ό κ°–λŠ” 것 μ—­μ‹œ gen κ³Ό gen1 이닀. 

 

console.log('next' in genFunc) // false
console.log('next' in gen) // true

console.log('return' in genFunc) // false
console.log('return' in gen) // true

 

yield ν‚€μ›Œλ“œμ˜ 역할은 ν˜ΈμΆœμžμ—κ²Œ ν‘œν˜„μ‹μ˜ 평가 κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜λŠ” 것, λ˜λŠ” ν•¨μˆ˜μ˜ 싀행을 μΌμ‹œ μ€‘μ§€μ‹œν‚€λŠ” 것이닀.

 

πŸ“ 평가 결과의 λ°˜ν™˜

μœ„μ˜ μ˜ˆμ œμ—μ„œ yield λ₯Ό ν˜ΈμΆœν•œ ν˜ΈμΆœμžλŠ” gen κ³Ό gen1 이며, λ”°λΌμ„œ 여기에 λ°˜ν™˜λœ 값이 μ €μž₯되게 λœλ‹€. 이 λ•Œ λ°˜ν™˜λ˜λŠ” 값은 value 와 done ν”„λ‘œνΌν‹°λ₯Ό κ°–λŠ” iterator result object 이닀. 

 

πŸ“ ν•¨μˆ˜μ˜ 싀행을 μΌμ‹œ 쀑지

next λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ yield ν‘œν˜„μ‹κΉŒμ§€ μ‹€ν–‰λ˜κ³  μΌμ‹œ μ€‘μ§€λœλ‹€. μ΄λ•Œ ν•¨μˆ˜μ˜ μ œμ–΄κΆŒμ΄ ν˜ΈμΆœμžμ—κ²Œ μ–‘λ„λ˜λŠ”λ°, 이 말의 μ˜λ―ΈλŠ” ν˜ΈμΆœμžκ°€ ν•„μš”ν•  λ•ŒκΉŒμ§€ ν•¨μˆ˜λ₯Ό μΌμ‹œ 쀑지 μƒνƒœλ‘œ λ‘μ—ˆλ‹€κ°€ ν•„μš”ν•  λ•Œ 싀행을 μž¬κ°œν•  수 μžˆλ‹€λŠ” 것이닀. 

 

또 ν•˜λ‚˜ μ€‘μš”ν•œ νŠΉμ§•μ€, next() μ•ˆμ— λ“€μ–΄κ°€λŠ” 인수 (μ „λ‹¬μΈμž) 의 νŠΉμ§•μ΄λ‹€. next λ©”μ„œλ“œμ— λ“€μ–΄κ°€λŠ” μΈμˆ˜λŠ” yield ν‘œν˜„μ‹μ„ ν• λ‹Ήλ°›λŠ” λ³€μˆ˜μ— ν• λ‹Ήλœλ‹€. (❗ 주의 : yield 의 평가 κ²°κ³Όκ°€ ν• λ‹Ήλ˜λŠ” 것이 μ•„λ‹ˆλ‹€! )

 

function* genFunc() {
    // 첫 next 호좜 μ‹œ μ—¬κΈ°μ„œ μ‹€ν–‰ 쀑단
    // value μ—λŠ” 1이 λ“€μ–΄κ°€λ©° x μ—λŠ” 아무것도 ν• λ‹Ήλ˜μ§€ μ•ŠμŒ
    // ν•΄λ‹Ή ν‘œν˜„μ‹μ€ λ‹€μŒ next 호좜 μ‹œ μ™„λ£Œλ¨
    const x = yield 1; 
    
    // λ‘λ²ˆμ§Έ next 호좜 μ‹œ μ „λ‹¬λ˜λŠ” μΈμˆ˜λŠ” x 에 할당됨
    // yield ν‘œν˜μ‹ μ‹€ν–‰ ν›„ 쀑단, value μ—λŠ” x + 10 듀어감, y μ—λŠ” 아무것도 ν• λ‹Ήλ˜μ§€ μ•ŠμŒ
    // ν•΄λ‹Ή ν‘œν˜„μ‹μ€ λ‹€μŒ next 호좜 μ‹œ μ™„λ£Œλ¨
    const y = yield (x + 10);
    
    // μ„Έλ²ˆμ§Έ next 호좜 μ‹œ x + 10 의 κ²°κ³Όκ°€ y 에 할당됨
    // 더 μ΄μƒμ˜ yield κ°€ μ—†μœΌλ―€λ‘œ ν•¨μˆ˜κ°€ λκΉŒμ§€ 싀행됨
    // return 값은 value 에 λ“€μ–΄κ°€λ©°, done 은 true 둜 μ „ν™˜ 
    // return 은 별닀λ₯Έ μ˜λ―Έκ°€ μ—†μœΌλ©° μ’…λ£Œν•  λ•Œ μ‚¬μš©λ¨
    return x + y;
}

const gen = genFunc();

// 첫번째 next 싦행 μ‹œμ—λŠ” 인수λ₯Ό 넣더라도 λ¬΄μ‹œλœλ‹€. 
console.log(gen.next()) // {value: 1, done: false}

// x 에 10 의 값이 할당됨 (아무 값도 넣지 μ•ŠλŠ”λ‹΄λ³€ NaN 이 value 둜 λ°˜ν™˜)
console.log(gen.next(10)) // {value: 20, done: false} 

// y 에 30 의 값이 할당됨
console.log(gen.next(30)) // {value: 40, done: true}

 

generator function 의 핡심은 next 와 yield λ₯Ό 톡해 ν•¨μˆ˜ ν˜ΈμΆœμžμ™€ ν•¨μˆ˜μ˜ μƒνƒœλ₯Ό 주고받을 수 μžˆλŠ” 것이닀. 

 

 

 

✨  참고자료

  • λͺ¨λ˜ μžλ°”μŠ€ν¬λ¦½νŠΈ Deep Dive (μœ„ν‚€λΆμŠ€) 
  • MDN

 

λŒ“κΈ€