이터레이터와 제너레이터
이터레이터와 제너레이터는 자바스크립트의 강력한 기능으로, 데이터 컬렉션을 처리하고 복잡한 비동기 흐름을 관리하는 데 사용됩니다.
이 절에서는 이 두 개념에 대해 자세히 알아보겠습니다.
이터레이터 (Iterator)
이터레이터는 데이터 컬렉션의 요소를 순회하기 위한 인터페이스입니다.
이터러블 프로토콜을 구현한 객체는 for...of
루프에서 사용할 수 있습니다.
이터러블 프로토콜
객체가 이터러블이 되려면 Symbol.iterator
메서드를 구현해야 합니다.
이 메서드는 next()
메서드를 가진 이터레이터 객체를 반환해야 합니다.
const myIterable = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
if (i < 3) {
return { value: i++, done: false };
}
return { done: true };
}
};
}
};
for (const item of myIterable) {
console.log(item); // 0, 1, 2
}
커스텀 이터러블
이터러블 프로토콜을 이용해 사용자 정의 객체를 순회 가능하게 만들 수 있습니다.
class FibonacciSequence {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let a = 0, b = 1, count = 0;
return {
next: () => {
if (count++ < this.limit) {
[a, b] = [b, a + b];
return { value: a, done: false };
}
return { done: true };
}
};
}
}
const fib = new FibonacciSequence(5);
for (const num of fib) {
console.log(num); // 1, 1, 2, 3, 5
}
제너레이터 (Generator)
제너레이터는 이터레이터를 생성하는 특별한 유형의 함수입니다.
function*
문법을 사용하여 정의하며, yield
키워드로 값을 생성합니다.
기본 문법
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().done); // true
제너레이터를 이용한 비동기 프로그래밍
제너레이터는 비동기 코드를 동기적으로 작성할 수 있게 해줍니다.
function* fetchUserData() {
const user = yield fetch('https://api.example.com/user');
const posts = yield fetch(`https://api.example.com/posts?userId=${user.id}`);
return { user, posts };
}
function runGenerator(gen) {
const it = gen();
function run(arg) {
const result = it.next(arg);
if (result.done) return result.value;
return result.value.then(run);
}
return run();
}
runGenerator(fetchUserData).then(data => console.log(data));
이 패턴은 async/await
의 전신이 되었습니다.
무한 시퀀스와 메모리 효율적인 데이터 처리
제너레이터를 사용하면 무한 시퀀스를 표현할 수 있고, 큰 데이터셋을 메모리 효율적으로 처리할 수 있습니다.
function* infiniteSequence() {
let i = 0;
while (true) {
yield i++;
}
}
const numbers = infiniteSequence();
console.log(numbers.next().value); // 0
console.log(numbers.next().value); // 1
// ... 무한히 계속
function* largeDataProcessor(data) {
for (const item of data) {
yield processItem(item);
}
}
async/await와 제너레이터
async/await
는 제너레이터와 프로미스를 기반으로 한 추상화입니다.
제너레이터를 사용한 비동기 제어 흐름 라이브러리(예 : co)는 이와 유사한 기능을 제공합니다.
const co = require('co');
co(function* () {
const result = yield Promise.all([
fetch('url1'),
fetch('url2')
]);
return result;
}).then(data => console.log(data));
실제 애플리케이션에서의 활용
- 데이터 스트리밍 : 대용량 데이터를 처리할 때 메모리 사용을 최적화할 수 있습니다.
function* streamData(url) {
const response = yield fetch(url);
const reader = response.body.getReader();
while (true) {
const { done, value } = yield reader.read();
if (done) break;
yield value;
}
}
- 페이지네이션 : API에서 대량의 데이터를 가져올 때 유용합니다.
function* paginate(url, pageSize = 10) {
let page = 1;
while (true) {
const response = yield fetch(`${url}?page=${page}&pageSize=${pageSize}`);
const data = yield response.json();
if (data.length === 0) break;
yield* data;
page++;
}
}
- 상태 관리 : 복잡한 상태 전이를 관리할 때 사용할 수 있습니다.
function* stateMachine() {
let state = 'initial';
while (true) {
switch (state) {
case 'initial':
state = yield 'next state?';
break;
case 'running':
state = yield 'still running, next?';
break;
case 'stopped':
return 'finished';
}
}
}
이터레이터와 제너레이터는 자바스크립트에서 강력하고 유연한 도구입니다. 이들을 통해 복잡한 데이터 흐름을 간단하게 표현하고, 비동기 작업을 더 직관적으로 관리할 수 있습니다. 대규모 데이터 처리, 비동기 프로그래밍, 상태 관리 등 다양한 시나리오에서 이 기능들을 활용하면 코드의 가독성과 효율성을 크게 높일 수 있습니다.
특히 제너레이터는 비동기 프로그래밍에서 강력한 도구로, async/await
구문의 기반이 되었습니다. 복잡한 비동기 로직을 동기적인 스타일로 작성할 수 있게 해주어, 코드의 흐름을 더 쉽게 이해하고 관리할 수 있게 합니다.
실제 애플리케이션 개발에서 이터레이터와 제너레이터는 데이터 스트리밍, 페이지네이션, 상태 관리 등 다양한 영역에서 활용됩니다. 이를 통해 메모리 사용을 최적화하고, 복잡한 비즈니스 로직을 더 명확하게 표현할 수 있습니다.
결론적으로, 이터레이터와 제너레이터는 현대 자바스크립트 개발에서 중요한 개념이며, 이들을 효과적으로 활용하면 더 강력하고 유지보수가 용이한 코드를 작성할 수 있습니다.