icon안동민 개발노트

이터레이터와 제너레이터


 이터레이터와 제너레이터는 자바스크립트의 강력한 기능으로, 데이터 컬렉션을 처리하고 복잡한 비동기 흐름을 관리하는 데 사용됩니다.

 이 절에서는 이 두 개념에 대해 자세히 알아보겠습니다.

이터레이터 (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));

실제 애플리케이션에서의 활용

  1. 데이터 스트리밍 : 대용량 데이터를 처리할 때 메모리 사용을 최적화할 수 있습니다.
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;
  }
}
  1. 페이지네이션 : 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++;
  }
}
  1. 상태 관리 : 복잡한 상태 전이를 관리할 때 사용할 수 있습니다.
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 구문의 기반이 되었습니다. 복잡한 비동기 로직을 동기적인 스타일로 작성할 수 있게 해주어, 코드의 흐름을 더 쉽게 이해하고 관리할 수 있게 합니다.

 실제 애플리케이션 개발에서 이터레이터와 제너레이터는 데이터 스트리밍, 페이지네이션, 상태 관리 등 다양한 영역에서 활용됩니다. 이를 통해 메모리 사용을 최적화하고, 복잡한 비즈니스 로직을 더 명확하게 표현할 수 있습니다.

 결론적으로, 이터레이터와 제너레이터는 현대 자바스크립트 개발에서 중요한 개념이며, 이들을 효과적으로 활용하면 더 강력하고 유지보수가 용이한 코드를 작성할 수 있습니다.