이 문서는 자바스크립트애서 Iterable, Iterator, 심화과정인 Generator를 알아보고 정리하기 위한 문서입니다.
이 글에서는 다음과 같은 내용을 다룹니다.
Iterable은 순회할 수 있는 객체, 반복가능한 객체를 의미합니다.
→ 객체를 for...of
구조에서 반복되는 값과 같은 순회 동작을 정의하거나 사용자 지정 가능
이러한 객체들은 Symbol.iterator
메서드를 구현하여 iterable protocol을 준수해야 합니다.
Well-Known Symbol
중 하나Iterator 객체는 iterator 결과 객체
를 반환하는 next()
메서드를 제공하여 iterator protocol에 부합하는 객체
next()
메서드를 호출할 때마다 { value, done }
형식의 객체를 반환{ value: undefined, done: true }
반환위에서 Iterable은 반복할 수 있는 객체를 의미한다고 했습니다
저희가 알고 있는 가장 대표적인 예시엔 배열이 있습니다.
Q. 그렇다면 배열도 Iterable인가요?
A. 넵, 맞습니다. 배열과 같이 ECMAScript의 일부 표준 내장 객체에는 Symbol.iterator가 구현된 경우가 있습니다.
몇가지 코드 예시를 보겠습니다.
const arr = [1, 2, 3, 4, 5]
for(let n of arr) {
console.log(n); // 정상출력
}
arr[Symbol.iterator] = null;
for(let n of arr) {
console.log(n); // TypeError: arr is not iterable
}
const str = "My name is KB";
for (const char of str) {
console.log(char);
// 'M', 'y', ' ', 'n', 'a', 'm', 'e', ' ', 'i', 's', ' ', 'K', 'B'
}
const mySet = new Set([1, 2, 3]);
for (const value of mySet) {
console.log(value);
// 1, 2, 3
}
const myMap = new Map([
["a", 1],
["b", 2]
]);
for (const [key, value] of myMap) {
console.log(key, value);
// a 1, b 2
}
const kb_iterator_example = {
from: 1,
to: 5
}
kb_iterator_example[Symbol.iterator]: function () {
return {
from: this.from,
to: this.to,
next() {
if( this.from <= this.to ) {
return { value: this.current, done: false };
} else {
return { done: true };
}
}
}
}
for (const num of kb_iterator_example) {
console.log(num); // 1, 2, 3, 4, 5
}
아래 Generator의 코드를 확인해보겠습니다.
function* KBGen() {
yield 1;
yield 2;
yield 3;
}
const kb_gen = KBGen();
console.log(kb_gen.next());
console.log(kb_gen.next());
console.log(kb_gen.next());
console.log(kb_gen.next());
const kb_gen = KBGen();
for(const value of kb_gen) {
console.log(value);
}
Q. 갑자기 웬 코드인가요?
갑자기 Generator 코드를 보면 당황스러울 수 있습니다. 하지만 iterable, iterator에서 보았던 굉장히 익숙한 내용들을 볼 수 있습니다.
next()
메서드를 통해 값을 하나씩 반환for...of
구조에서 반복되는 값과 같은 순회 동작위 내용을 통해 Generator를 통해 반환되는 Generator객체는 제너레이터 함수를 호출하면 반환되는 제너레이터 객체는 Iterable이자 Iterator인 객체임을 알 수 있습니다.
function*
(별표가 붙은 함수) 키워드로 정의되는 특수한 함수next()
메서드를 호출할 때마다 yield
표현식 혹은 함수 블록의 끝까지 실행yield
를 만나면 해당 값을 { value, done: false }
형태로 반환하고 실행이 일시 중단next()
를 호출하면 중단된 지점부터 재개yield
가 소비되거나, 함수 블록 끝에 도달하면 { value: undefined, done: true }
가 반환됨Q. Generator가 어떤 것인지 이해했습니다. 그렇다면 Generator는 어떤 용도로 사용할 수 있나요?
특징 중 가장 중요한 것은 중단과 재개 매커니즘
입니다.
→ 여러 번에 걸쳐서 함수의 상태를 유지하며 실행할 수 있음을 의미
몇가지 예시를 살펴보겠습니다.
무한수열
무한대로 증가하는 시퀀스를 한 번에 전부 메모리에 올릴 수는 없지만, Generator를 사용하면 필요할 때마다 숫자를 하나씩 생성
// 무한히 증가하는 숫자를 내보내는 제너레이터
function* infiniteCounter() {
let i = 0;
while (true) {
yield i++;
}
}
// 필요한 순간에만 next()로 값을 요청
const counter = infiniteCounter();
console.log(counter.next().value); // 0
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
// ... 계속해서 무한정 꺼낼 수 있음
대용량 데이터 파일 지연 로드
수십만 줄짜리 로그 파일(log.txt
)을 한꺼번에 읽으면 메모리 문제가 생길 수 있습니다. Generator를 통해 필요한 만큼만 줄 단위로 읽어서 처리
const fs = require("fs");
function* readLargeFileByLine(filePath) {
const fileData = fs.readFileSync(filePath, "utf-8");
// 실제 대규모 데이터를 처리할 땐 stream을 사용하거나 chunk 단위로 처리하는 방식을 쓸 수 있음
// 여기서는 개념 시연을 위해 readFileSync를 예시로 사용
const lines = fileData.split("\n");
for (const line of lines) {
yield line; // 한 줄씩 반환
}
}
const lineReader = readLargeFileByLine("log.txt");
// 필요한 시점에만 한 줄씩 가져옴
console.log(lineReader.next().value); // 파일 첫 번째 줄
console.log(lineReader.next().value); // 파일 두 번째 줄
// ...
ES6이전(async/await
가 도입되기 이전)엔 Generator와 yield
를 활용하여 Promise 기반 비동기를 마치 동기 코드처럼 순차적으로 작성하는 방식을 선택했습니다.
하지만 async/await
가 위와 같은 기능을 대부분 대체했지만, 여전히 제너레이터가 커스텀 러너를 만들거나 복잡한 상태 기계(state machine)를 구현
// ex) Redux-Saga
import { call, put, takeEvery } from 'redux-saga/effects';
function* fetchUserSaga(action) {
const user = yield call(fetchUserApi, action.payload.userId);
yield put({ type: "USER_FETCH_SUCCESS", user });
}
function* mySaga() {
yield takeEvery("USER_FETCH_REQUEST", fetchUserSaga);
}
iterable
, iterator
, generator
개념을 공부해보았습니다.
실제 개발 상황에서는 대부분 async/await
로 비동기 로직을 처리하기 때문에, 특별한 요구사항이 없으면 generator
구문을 활용하는 빈도가 낮았습니다.
그러나 이번에 문서를 정리하며, generator
가 어떻게 동작하고 iterable
, iterator
개념을 어떻게 바탕으로 하는지 구체적으로 이해할 수 있었습니다.
특히 generator
만의 지연 실행(lazy evaluation), 상태 제어 등 독특한 특징이 분명한 장점을 제공한다는 점이 흥미로웠습니다.
다음에도 더 알차고 체계적인 정리로 다시 찾아뵙겠습니다.
읽어주셔서 감사합니다!