이 글은 2년차 예비 프론트엔드 개발자가 쓴 글로, 틀린 부분이 있을 수 있습니다.
그런 부분은 댓글이나 메일로 알려주시면 감사하겠습니다.
프론트엔드 개발자라면 redux-saga
라는 redux middleware를 본 적이 있을 것이다.
이 때 generator
라는 문법을 사용하는데, generator를 그저 함수를 순서대로 사용할 수 있게 하는 문법
정도로 기억하고 있을 것이다. 필자도 초기에는 그렇게 생각하고 있었다.
그럼 이 generator는 어떻게 돌아가는지 궁금했다. 그것에 대해 알아보자
mdn에서 말하는 itereator입니다.
자바스크립트에서 반복자(Iterator)는 시퀀스를 정의하고 종료시의 반환값을 잠재적으로 정의하는 객체입니다.
더 구체적으로 말하자면, 반복자는 두 개의 속성(value, done)을 반환하는 next() 메소드 사용하여 객체의 Iterator protocol을 구현합니다.
시퀀스의 마지막 값이 이미 산출되었다면 done 값은 true 가 됩니다. 만약 value값이 done 과 함께 존재한다면, 그것은 반복자의 반환값이 됩니다.
여기서 시퀸스는 일련의 연속적인 사건들
또는 사건이나 행동 등의 순서
라고 합니다.
그럼 iterator
는 연속적인 사건들을 만들어내고, 반환값을 잠재적으로 정의하는 객체라고 할 수 있겠습니다.
이제 iterator를 만들면 사건(값)들이 여러개 들어가게 되고, next method를 이용해서 사건을 넘기는 것
입니다. 사건을 넘기면, 그 사건이 실행 된 값이 return 됩니다.
그리고 만약 { value: something, done: true }가 되었을 경우, iterator의 사건이 모두 일어난 것입니다.
실제로 iterator는 어떤 객체에서든 만들 수 있습니다.
객체에서 [Symbol.iterator]
에 객체를 리턴하는 함수를 만들면 됩니다.
그럼 [Symbol.iterator]는 객체의 iterator가 됩니다.
let index = -1;
let array = [1,2,3]
const test = {};
array[Symbol.iterator] = function(){
return {
next: function(){
if(index < array.length - 1) {
index++;
return { value: array[index], done: false }
}
else return {value: array[array.length -1], done: true}
}
}
}
iterator
가 있는 객체는 모두 spread 연산자
나 for...in
그리고 for...of
를 사용할 수 있습니다.
그럼 iterator가 반복자 였으니 iterable
은 반복 가능한 객체
일 것입니다.
iterable은 iterator를 포함한 객체라고 설명할 수 있습니다.
그래서 iterable은 spread 연산자, for...in, for...of를 사용할 수 있습니다.
generator
는 말 그대로 생성자
입니다. generator function은 리턴값으로 generator를 반환 generator는 iterator와 비슷하게 작동합니다.
밑은, MDN에서의 generator 설명입니다.
실행이 연속적이지 않은 하나의 함수를 작성함으로서 개발자가 iterative algorithm을 정의할 수 있게 해줍니다.
생성자 함수는 function 문법을 사용하여 작성됩니다. 생성자 함수가 최초로 호출될 때, 함수 내부의 어떠한 코드도 실행되지 않고, 대신 생성자라고 불리는 반복자 타입을 반환합니다.
생성자의 next 메소드를 호출함으로서 어떤 값이 소비되면, 생성자 함수는 yield 키워드를 만날 때까지 실행됩니다.
generator는 다음과 같이 만들 수 있습니다.
function* 이름(매개변수){
yield work1();
yield work2();
// next method 실행마다 work1, work2 순서대로 실행됨.
}
이름().next(); // work1 실행
iterator를 생성하기 힘들어지니까 간단하게 생성할 수 있게 도와주는 녀석인겁니다.
예시로 MDN에 있는 예시입니다.
var myIterable = {
*[Symbol.iterator]() {
// [Symbol.iterator]: function*(){...}와 같다.
yield 1;
yield 2;
yield 3;
}
}
이 특별한 문법은, 다른 generator나 iterable 객체의 iterator를 위임할 때 사용합니다.
MDN의 예시를 가져오겠습니다.
function* func1() {
yield 42;
}
function* func2() {
yield* func1();
}
const iterator = func2();
iterator.next(); // { value: 42, done: false }
func2에서 func1의 iterable 객체를 generator 객체 안에서 순회시키기 위해서 사용합니다.