Typescript - 15

이강민·2022년 7월 22일
0

Typescript

목록 보기
15/16
post-thumbnail

생성기 이해하기

ESNext자바스크립트와 타입스크립트는 yield라는 키워드를 제공한다.
yield는 마치 return 키워드처럼 값을 반환하는데 반드시 function * 키워드를 사용한 함수에서만 호출 할 수 있다.
이렇게 만들어지는 함수를 생성기(generator)라고 한다.

다음과 같이 생성기를 만들어 보자

export function * generator(){
  console.log('generator start')
  let value = 1
  while(value < 4)
    yield value ++
    console.log('generator finished')
} 

import {generator} from './export'

for(let value of generator())
console.log(value)

import 된 코드를 보면 앞서 배열의 반복기에서 사용했던 for..of구문을 사용할 수 있는 것을 알 수 있다.

setInterval 함수와 생성기의 유사성

생성기가 동작하는 방식을 세미코루틴이라고 한다.
(semi-coroutine, 반협동 루틴)
세미코루틴은 타입스크립트처럼 단일 스레드single-thread로 동작하는 프로그래밍 언어가 마치 다중스레드multi-thread로 동작하는 것처럼 보이게 하는 기능을 한다.

setInterval 함수를 이용해서 세미코루틴 동작방식 알아보기

const period = 1000
let count = 0 
console.log('program started..')
const id = setInterval(()=>{
  if(count >= 3){
    clearInterval(id)
    console.log('program finished...')
  }
  else
    console.log(++count)
},period)

다음 위 코드는 1 ~ 3까지 value를 반환하는 함수로 결과만 보면 생성기와 구분하기 힘들다. 그러나 동작방식을 보면 전혀다른 식으로 동작한다.

세미코루틴과 코루틴의 차이
메모리나 CPU를 제작할 때 사용하는 소재를 반도체라고 하는 데 이때 쓰는 반은 반대의 개념이 아닌 절반(semi)을 의미한다.
생성기는 세미코루틴이라고 하는데 생성기는 절반만 코루틴이라는 뜻이다. 코루틴은 애플리케이션 레벨의 스레드이다. 스레드는 원래 운영체제가 제공하는 갯수가 제한된 서비스라서 과다하게 사용하면 운영체제에 무리를 주게 된다.
코루틴은 이러한 문제를 해결하기 위해 등장하였으며 스레드를 무리없이 사용할 수 있다. 코루틴은 스레드임으로 일정 주기에 따라 자동으로 반복해서 실행된다. 반면에 생성기는 절반만 코루틴이라 반복자의 next메서드가 호출 될 때 한번 실행된다. 만약, next메서드가 while문에서 반복해서 호출된다면, 생성기는 next호출 할 때 한번 실행되고 곧바로 멈춘다.
생성기는 자동으로 반복 실행되지 않음으로 세미코루틴이라고 하는 것이다.

function * 키워드

생성기는 오직 function*키워드로 선언해야 하므로 화살표 함수로는 생성기를 만들 수 없다.

function에 *를 붙인것이 아니라 function*자체가 키워드이다.
function과 별표(*)사이에 공백은 없어도 되고 여러 개 있어도 상관없다.

yield 키워드

생성기 함수 안에서는 yield문은 사용할 수 있다. yield는 연산자(operator)형태로 동작하며 다음 두 가지 기능을 한다.
1. 반복기를 자동으로 만들어 준다.
2. 반복기 제공자 역할도 수행한다.

export function* rangeGenerator(from : number, to : number){
  let value = from
  while(value < to){
    yield value ++
  }
}

import {rangeGenerator} from './export'

//yield를 사용하여 만든 생성기는 아래와 같이 사용이 가능하다.
let iterator = rangeGenerator(1, 3 + 1)
//while 패턴으로 동작하는 생성기
while(1){
const {value, done} = iterator.next()
if(done) break
console.log(value)
}
// for...of 패턴으로 동작하는 생성기
for(let value of rangeGenerator(4, 6 + 1))
console.log(value)

반복기 제공자의 메서드로 동작하는 생성기 구현

이전 챕터에서 StringIterable메서드를 만들어서 반복기 제공자를 구현하였는데 생성기는 반복기를 제공하는 반복기 제공자로써도 동작하므로, 생성기를 사용하면 StringIterable 클래스를 간략히 구현이 가능하다.


export class IterableUsingGenerator<T> implements Iterable<T>{
  constructor(private values:T[] = [], private currentIndex:number = 0){}
  [Symbol.iterator] = function*(){
    while(this.currentIndex < this.values.length)
    yield this.values[this.currentIndex++]
  }
}

위와 같이 정의하면 이전 챕터의 StringIterable과 동일하게 동작하는 것을 확인 할 수 있다.

import {IterableUsingGenerator} from './export'
for(let item of new IterableUsingGenerator([1,2,3]))
console.log(item)

for(let item of new IterableUsingGenerator(['hellow', 'world', ' !']))
  console.log(item)

yield * 키워드

타입스크립트는 yield 키워드 위에 을 붙인 yield키워드를 제공한다. yield는 단순히 값을 대상으로 동작하지만 yield*는 다른 생성기나 배열을 대상으로 동작한다.

function* gen12(){
  yield 1
  yield 2
}

export function* gen12345(){
  yield* gen12()
  yield* [3,4]
  yield 5
}

// 사용 부분
import {gen12345} from './export'
for(let value of gen12345())
 console.log(value)

위 코드의 동작을 살펴보면 다음과 같이 동작한다.
먼저 for 문에서 gen12345가 호출되어 함수가 실행고 처음 yield*에 의해 gen12 함수가 실행된다. gen12의 2행에서 yield 1이 실행되고 코드가 정지한다.
그러나 다시 for문에 의해 gen12345를 다시 호출하고 gen12가 호출되어 이번에는 yield2를 실행한다. 다시 gen12345를 실행하고 1행의 gen12에서 실행할 문장이 없음으로 이어서 [3,4]에서 3을 생성하고 멈춘다.
이렇게 4와 5를 생성한뒤 for문이 종료되어 프로그램이 끝난다.

yield 반환값

yield 연산자는 값을 반환한다.

다음 코드는 yield 연산자의 반환값을 select라는 변수에 저장한다.

export function* gen(){
  let count = 5
  let select = 0
  while(count--){
    select = yield `you select ${select}`
  }
}

export const random = (max, min = 0) => 
Math.round(Math.random() * (max-min)) + min

yield 연산자의 반환값은 반복기의 next 메서드 호출 때 매개변수에 전달하는 값이다. next 메서드 호출 때 난수를 생성해서 전달한다.


import {random, gen} from './export'
const iter = gen()
while(true){
  const {value, done} = iter.next(random(10,1))
  if(done) break
  console.log(value)
}

코드를 실행하면 첫 줄 외에 다른 줄은 모두 난수가 출력된다.
첫 줄은 항상 0이 출력 되는 데 select를 0으로 초기화 했기 때문이다.

profile
AllTimeDevelop

0개의 댓글