Generator

이효범·2022년 4월 26일
0

ES6 - OOP & FRP

목록 보기
12/15
post-thumbnail

Generator에 대해 간략히 알아본다.

Generator

Generator는 Asynchronous 프로그래밍에 굉장히 요긴하게 사용되는 개념이다.
다음 코드를 보면서 Generator를 이해해보도록 하자.

// const heroes = ['Superman', 'Batman', 'Ironman'];

// es6 이전
// for (let i = 0; i < heroes.length; i++) {
//  console.log(heroes[i]); 
// }

// es6 
// for (let hero of heroes) {
//  console.log(hero) 
// }


for (let hero of heroes()) {  // heroes를 바로 호출
 console.log(hero); 
}
// Superman Batman Ironman
// 이게 어떤 의미일까 ?
// for...of 문장은 Iterator.

function* heroes() {  // function 뒤에 *이 붙어있다.
  yield 'Superman'; 
  yield 'Batman';
  yield 'Itonman';
}

제네레이터의 기본적인 동작 메커니즘은 호출했다고 하여 한번에 값들이 다 나오는 것이 아니라,
호출할 때마다 yield를 찾아서 이전까지 진행되고 있던 그 값을 다시 yield에서 연결하여 그 다음번에 위치하고 있는 yield를 실행하여 리턴하는 것이다.

즉, 제너레이터는 일반적인 형태의 함수가 아닌데,
일반함수(서브루틴)은 caller가 함수를 call하면, callee 함수는 콜스택에서 작업을 처리하고 작업 처리 후에는 콜스택에서 사라진다(콜스택을 나간 함수를 다시 호출한다면 처음부터 다시 실행될 것이다).

반면 제너레이터는 callee 함수의 진입점을 지정할 수 있다. 이는 해당 컨텍스트를 저장하고 반환하는 특수한 구조를 갖기 때문이며, 결국 caller와 callee가 콜스택에 들어가 수행되고 서로 호출하는 구조가 제너레이터이다.

조금 더 복잡한 예시를 보자.

const heroTeam = {
  members: 10,
  department: 'Earth Cap',
  lead: 'X-man',
  manager: 'Superman',
  employees: ['Birdman', 'Batman']
}

function* heroIterator(team) {
 yield team.employees;
 yield team.manager;
 yield team.lead; 
}

for (let item of heroIterator(heroTeam)) {
 console.log(item); 
}
// ['Birdman', 'Batman'] 
// Superman 
// X-man

위의 예시처럼 제네레이터를 이용하면 오브젝트의 필드들을 뽑아서 이터레이터 방식으로 순회하며 출력을 할 수 있다는 큰 장점을 가진다.


Delegation of Generator

이터레이터를 제네레이터 안에 다시금 포함시키면 제네레이터 속에 다른 제네레이터가 포함될 수 있다. 이러한 방식을 Delegation 방식이라고 한다.

const employees = {
  guard: 'Birdman',
  worker: 'Batman'
}

const heroTeam = {
  members: 10,
  department: 'Earth Cap',
  lead: 'X-man',
  manager: 'Superman',
  employees
}

function* employeeIterator(team) {
 yield team.guard;
 yield team.worker; 
} 
function* heroIterator(team) {
 yield team.manager;
 yield team.lead; 
 yield* employeeIterator(employees);  // yield에 *이 붙어있다. 이를 서브루틴이라고 하며 "또다시 새로운 제네레이터를 시작한다" 라는 의미를 갖는다. 이러한 시점에서 제어권은 서브루틴으로 넘어가게 된다.   
}

for (let item of heroIterator(heroTeam)) {
 console.log(item); 
}
// Superman 
// X-man
// Birdman
// Batman

위의 코드는 Delegation 방식을 따르는 제네레이터 함수에 대한 코드이다.
그런데 위의 코드의 가독성을 좀 더 높여줄 수는 없을까?
따라서 좀 더 좋은 방법, 좀 더 단순하고 직관적인 방법을 소개해보고자 한다.


Symbol Iterator

Symbol Iterator도 ES6에 새로 도입된 메커니즘이다.
Symbol 메커니즘을 이용하면 위 코드를 좀 더 깔끔하게 리팩토링 할 수 있다.

이 블로그에 Symbol Iterator에 대한 또 다른 정리글이 존재한다.
다음 링크를 참고하자.
순회와 이터러블 : 이터레이터 프로토콜

const employees = {
  guard: 'Birdman',
  worker: 'Batman',
  [Symbol.iterator]: function* () { // 제네레이터
    yield this.guard;
    yield this.worker;
  },
}

const heroTeam = {
  employees,
  members: 10,
  department: 'Earth Cap',
  lead: 'X-man',
  manager: 'Superman',
  [Symbol.iterator]: function* () { // 제네레이터
    yield this.members;
    yield this.lead;
    yield this.department;
    yield* this.employees;
  },
}


for (let item of heroTeam) {
 console.log(item); 
}
// 10
// Earth Cap
// X-man
// Birdman
// Batman

위처럼 기존의 코드보다 좀 더 깔끔하게 리팩토링 되었다.
이러한 방식이 바로 ES6에 새로 추가된 심볼 이터레이터 문법이다.

profile
I'm on Wave, I'm on the Vibe.

0개의 댓글