프로그래밍/JavaScript

[ES6] 제너레이터

mhko411 2021. 8. 4. 23:57
728x90

요즘 함수형 프로그래밍에 흥미가 생겼다. 리액트로 프로젝트를 진행하면서 좀 더 좋은 코드를 작성하기위해 노력을 하고있는데 이 과정에서 함수형 프로그래밍의 필요성을 알게되었다. 아직 함수형 프로그래밍에 대해 완벽히 이해하지는 못했지만 조금씩 공부를 하던 중에 ES6에서 도입된 제너레이터 함수라는 재미있는 개념이 등장해서 정리를 해보려고 한다.


일반적인 함수를 호출 했을 때 제어권이 함수에게 넘어가고 함수가 모두 실행이 되었을 때 다시 제어권을 함수 호출자가 가져가게된다. 하지만 제너레이터 함수를 호출하면 제어권을 함수가 가져가는 것이 아니라 함수 호출자에게 제어권을 양도(yield)한다. 

 

 

다음은 1부터 10까지의 수 중에서 홀수를 출력하는 함수를 작성한 것이다.

1부터 10까지의 수가 포함된 배열인 numbers를 전달받아 순차적으로 탐색을 진행하면서 홀수를 출력하고있다. 이때 제어권은 getOddNumbers라는 함수에 넘어가고 함수가 모두 실행된 이후에 다시 제어권을 돌려받는다.

function getOddNumbers(numbers) {
  for (number of numbers) {
    if (number % 2) console.log(number);
  }
}

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
getOddNumbers(numbers);

 

이제 같은 결과를 나타내는 함수를 제너레이터 함수로 구현해보자.

제너레이터 함수를 작성할 때는 화살표 함수를 사용할 수 없으며 function과 함수 이름 사이에 * 표시를 한다. 아래의 코드에서 제너레이터 함수를 호출하면 [GeneratorFunction: getOddNumbers]이 출력된다. 이후 제너레이터 함수에 next()를 실행하면 { value: 1, done: false } 이 출력된다.

function* getOddNumbers(numbers) {
  for (number of numbers) {
    if (number % 2) yield number;
  }
}

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
console.log(getOddNumbers(numbers));
console.log(getOddNumbers(numbers).next());

위에서 작성한 코드의 의미는 제어권을 함수 호출자에게 위임하고 함수 호출자가 다음 값을 탐색하라고 명령했을 때 numbers를 순회하면서 홀수일 때만 출력하게 되는 것이다. 지금까지 개인적으로 이해한 것은 일반 함수에서는 return을 만나게 되면 해당 값을 반환하면서 함수가 종료되는데 yield는 함수 호출자가 명령을 하면 해당 값을 반환하도록 한 것이라고 이해하였다.


제너레이터 함수 활용하기

이제 제너레이터 함수를 통해 1부터 10까지의 수에서 홀수만 2를 곱하여 출력하는 함수를 작성하면서 제너레이터 함수를 더 이해해보자.

먼저 홀수를 판별하는 함수인 filter라는 제너레이터 함수를 정의하였다. 이때 조건을 판별할 함수와 숫자 배열을 입력받은 뒤에 func 함수의 조건에 맞을 때 number를 반환하고 있다. 또한 map이라는 제너레이터 함수에서는 홀수들에 대해 x2를 해주고 있다.

이제 filter와 map을 for문을 통해 한 단계씩 실행하면서 결과값을 출력하도록 한다.

function* filter(func, numbers) {
  for (number of numbers) {
    if (func(number)) yield number;
  }
}

function* map(func, numbers) {
  for (number of numbers) {
    yield func(number);
  }
}

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

for (number of map(
  (n) => n * 2,
  filter((n) => n % 2, numbers)
)) {
  console.log(number);
}

 

또는 위의 코드를 아래와 같이 변경할 수도 있을 것이다. filter와 map을 실행한 결과를 numbers로 다시 받고 이를 탐색해보면 위의 함수와 똑같은 결과가 나온다.

function* filter(func, numbers) {
  for (number of numbers) {
    if (func(number)) yield number;
  }
}

function* map(func, numbers) {
  for (number of numbers) {
    yield func(number);
  }
}

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

numbers = map(
  (n) => n * 2,
  filter((n) => n % 2, numbers)
);

for (number of numbers) console.log(number);

제너레이터 함수에 대해 공부하면서 함수 호출자가 실행해줘야 다음으로 진행된다는 것이 참 재미있었다. 앞으로 제너레이터 함수도 많이 활용하면서 함수형 프로그래밍에서 어떻게 활용되는지 공부해봐야겠다.

'프로그래밍 > JavaScript' 카테고리의 다른 글

[JS] null과 undefined의 차이  (0) 2021.08.23
[ES6] Promise  (0) 2021.08.12
[JS] 브라우저의 렌더링 과정  (0) 2021.08.03
[JS] 함수  (0) 2021.07.19
[JS] Closure  (0) 2021.07.10