[모던JS: Core] 제너레이터

KG·2021년 5월 31일
1

모던JS

목록 보기
23/47
post-thumbnail

Intro

본 포스팅은 여기에 올라온 게시글을 바탕으로 작성되었습니다.
파트와 카테고리 동일한 순서로 모든 내용을 소개하는 것이 아닌, 몰랐거나 새로운 내용 위주로 다시 정리하여 개인공부 목적으로 작성합니다.
중간중간 개인 판단 하에 필요하다고 생각될 시, 기존 내용에 추가로 보충되는 내용이 있을 수 있습니다.

제너레이터

앞서 다룬 함수들은 0개 혹은 하나의 값을 반환하는 일반 함수가 주였다. 여기서 하나의 값이란 리턴하는 값의 형태가 고정적인 포맷임을 의미한다. 즉 다음과 같이 어떤 함수가 return [1, 2, 3] 처럼 배열을 사용해 값을 반환하는 경우, 3개의 값을 반환하는 것이 아니라 배열이라는 하나의 값을 반환한다는 입장에서, 일반 함수는 값을 반환하지 않거나(void) 하나의 값 만을 반환할 수 있다.

하지만 제너레이터(generator)라고 불리는 특수 함수를 사용한다면 여러 개의 값을 필요에 따라 하나씩 반환할 수 있다. 제너레이터와 앞서 다룬 이터러블(Itreable) 객체를 함께 사용한다면 손쉽게 데이터 스트림을 만들 수 있다.

1) 제너레이터 함수

제너레이터 함수를 만들기 위한 특별한 문법에 대해 먼저 살펴보자. 다음과 같이 함수를 선언할 때 사용하는 키워드 function*기호를 함께 붙여 사용하고 있는 것을 볼 수 있다. 또한 함수 내부에서는 yield라는 키워드를 사용하고 있음에 주의하자.

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

제너레이터 함수는 일반 함수와 동작 방식이 다르다. 제너레이터 함수를 호출하면 코드가 실행되지 않고, 대신 실행을 처리하는 특별한 객체인 제너레이터 객체가 반환된다.

let generator = generateSequence();
console.log(generator);	// [object Generator]

즉 제너레이터 함수를 호출하면 함수 본문 코드가 호출되는 것이 아닌 제너레이터 객체가 반환된다. 그리고 해당 객체는 이전에 다룬 [Symbol.iterator] 속성과 유사하게 내부 메서드로 next()를 가지고 있다.

제너레이터 객체가 가지고 있는 next() 메서드를 호출하면, 가장 가까운 yield <value>문을 만날 때까지 실행이 지속된다. 이때 <value>는 생략이 가능하고, 생략 시 반환값은 undefined가 된다. 이후 yield <value>문을 만나면 실행이 멈추고 산출하고자 하는 값인 value가 바깥 코드에 반환된다. 이때 next() 메서드가 반환하는 값은 iterator 객체와 동일하게 항상 두 프로퍼티를 가진 객체를 반환하게 된다.

  • value : 산출값
  • done : 함수 코드 실행이 끝났으면 true, 아니면 false

위에서 예시로 구현한 generateSequence 제너레이터 함수를 순차적으로 호출하면 다음과 같이 작동한다.

let one = generator.next();
console.log(one);	// { value: 1, done: false }

처음으로 next() 메서드를 통해 제너레이터 함수를 실행하게 되면 다음과 같이 첫 번째 yield문을 만나고 실행을 멈춘 상태가 된다. 그리고 아직 함수가 종료되지 않았기 때문에 반환하는 값에서 done 프로퍼티는 false 값을 가진다.

다시 generator.next()를 호출하면 실행이 재개되고 다음 yield를 반환하며, 위에서 보인것과 동일하게 작동한다.

let two = generator.next();
console.log(two);	// { value: 2, done: false }

또 한번 generator.next() 호출하면 실행은 return문에 다다르고, 때문에 함수는 종료되며 done 프로퍼티는 함수가 종료되었기 때문에 true 값을 담게 된다.

let three = generator.next();
console.log(three);	// { value : 3, done: true }

제너레이터 함수가 정상적으로 종료되었기 때문에 이 시점에서는 다시 generator.next() 호출하더라도 아무 소용이 없다. 이때 반환되는 값은 오직 { value: undefined, done: true }만 계속해서 리턴된다.

2) 제너레이터와 이터러블

위에서 제너레이터 함수를 설명하면서 이터러블(Iterable) 객체 및 [Symbol.iterator] 프로퍼티를 함께 언급했다. 이를 통해 짐작할 수 있듯이 제너레이터 객체는 Iterable 객체이다. 따라서 for...of 반복문을 통해 값을 얻을 수도 있다.

function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();

for(let value of generator) {
  console.log(value);	// 1, 2 출력
}

위오 같이 반복문을 통해 접근한다면 일일이 next() 메서드를 호출하는 것보다 편하게 접근이 가능하다. 그러나 출력 결과를 보면 알겠지만, 이 경우에 return 3이 출력되지 않는다는 점에 주의해야 한다. 그 이유는 for...of 반복문은 이터레이션의 상태가 done: true 일 때는 마지막 value는 무시하기 때문이다. 만약 for...of를 사용해서 모든 값이 출력되게 하고 싶다면 앞과 똑같이 yield를 사용해 값을 반환하도록 변경하면 된다.

function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}

...

또한 제너레이터 객체는 Iterable 객체이기 때문에 전개 문법(...) 같은 기능 역시 동일하게 적용할 수 있다.

let seqeunce = [0, ...generateSequence()];

console.log(sequence);	// 0, 1, 2, 3

3) 이터러블 대신 제너레이터 사용하기

iterator 객체를 다룬 파트에서 반복 가능한 객체 range를 구현했던 코드를 다시 가져와보자.

let range = {
  from: 1,
  to: 5,
  
  [Symbol.iterator]() {
    return {
      current: this.from,
      last: this.to,
      
      next() {
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
}

console.log([...range]);	// 1, 2, 3, 4, 5

이때 [Symbol.iterator] 프로퍼티 대신 제너레이터를 반환하도록 하면 더욱 쉽게 Iterable 객체를 만들 수 있다. 이는 결코 우연이 아닌데, 애초에 제너레이터 문법이 조금 더 쉽게 이터레이터를 구현하기 위해 도입된 기능이기 때문이다.

이처럼 제너레티어가 기존 Iterable 객체에 녹아들 수 있는 이유는 위에서 살펴보았듯이 다음 두 조건을 모두 충족하기 때문이다.

  • next() 메서드를 가지고 있음
  • 반환값의 형태가 { value: ..., done: Boolean}을 만족

4) 제너레이터 컴포지션

제너레이터 컴포지션은 제너레이터 안에 제너레이터를 임베딩할 수 있게 하는 제너레이터의 특별한 기능이다. 다음 제너레이터 함수는 연속된 숫자를 생성하는 제너레이터 객체를 반환한다.

function* ggenerateSequence(start, end) {
  for (let i = start; i <= end; i++) yield 1;
}

그리고 이 함수를 기반으로 우리는 조금 더 복잡한 값을 연속해서 생성하는 제너레이터 함수를 만들 수 있다. 이를 컴포지션, 즉 합성한다라고 표현한다. 합성된 제너레이터 함수는 위에서 구현한 제너레이터 함수를 가지고 구현하며 다음 3가지 기능을 가지고 있는 제너레이터 함수가 된다.

  • 처음엔 숫자 0부터 9까지 생성
  • 이어서 알파벳 A부터 Z까지 생성
  • 이어서 알파벳 a부터 z까지 생성
function* generatePasswordCodes() {
  yield* generateSequence(48, 57);
  yield* generateSequence(65, 90);
  yield* generateSequence(97, 112);
}

let str = '';

for(let code of generatePasswordCodes()) {
  str += String.fromCharCode(code);
}

이때 컴포지션을 적용하는 경우 yield* 키워드를 사용하고 있음에 주의하자. yield* 키워드는 실행을 다른 제너레이터에게 위임하는 역할을 한다. 여기서 위임이란, yield* gen이 제너레이터 gen을 대상으로 반복을 수행하고, 그로 인해 산출되는 값들을 바깥으로 전달한다는 것을 의미한다.

물론 위 함수는 다음과 같이 중첩 제너레이터 형식으로 선언하더라도 동일한 결과를 얻을 수 있다.

function* generatePasswordCodes() {
  for (let i = 48; i <= 57; i++) yield i;
  for (let i = 65; i <= 90; i++) yield i;
  for (let i = 97; i <= 122; i++) yield i;
}

제너레이터 컴포지션을 사용하면 한 제너레이터의 흐름을 자연스럽게 다른 제너레이터에 삽입할 수 있다는 장점이 있다. 또한 제너레이터 컴포지션을 사용하면 중간 결과 저장 용도의 추가 메모리가 필요하지 않다.

만약 일반 함수로 여러 개의 함수를 만들고, 그 결과를 어딘가에 저장한 후 다시 그 결과를 조합하여 반환하도록 설계한다면 위와 같이 동일한 기능을 하는 함수를 만들 수 있다. 그러나 이 경우에는 중간 결과를 저장하기 위한 별도의 공간이 필요하다.

5) yield를 사용한 제너레이터 정보교환

지금까지 제너레이터를 기존 Iterable 객체의 기존 쓰임과 비교해 어떤 부분에서 특장점이 있는지 살펴보았다. 이 외에도 제너레이터는 더 강력하고 유연한 기능을 제공한다.

yield 키워드는 양방향 통신을 지원한다. 즉 yield는 결과를 바깥으로 전달할 뿐만 아니라 값을 제너레이터 안으로 전달하는 역할도 수행한다. 이때 값을 안팎으로 전달하려면 generator.next(arg)를 호출해야 한다. 이때 인수 argyield의 결과가 된다. 예시를 통해 무슨 말인지 자세히 살펴보자.

function* gen() {
  // yield를 통해 "2 + 2 = ?" 라는 질문을 밖으로 던지고 대기
  let result = yield "2 + 2 = ?";
  console.log(result);
}

let generator = gen();

// yield는 value 값을 반환 => "2 + 2 = ?"
let question = generator.next().value;

generator.next(4);	// 4라는 결과를 제너레이터 안으로 전달

언뜻 보면 위 코드의 흐름이 이해가 안 갈 수 있다. 위의 코드는 다음의 흐름으로 동작한다.

  1. generator.next()를 처음 호출할 땐 항상 인수가 없어야 한다. 인수가 넘어오더라도 무시된다.
  2. generator.next()를 호출하면 실행이 시작되고 첫 번째 yield "2+2=?"의 결과가 반환되어 question에 저장된다. 그리고 이 시점에서 result에 값이 담기기 전인 yield문에서 실행을 멈추고 대기한다.
  3. 그 후 generator.next(4)에서 제너레이터가 다시 시작되고 인수로 전달된 4result에 할당된다.
  4. 콘솔에 result에 저장된 4가 출력되고 함수 본문은 종료된다.

외부 코드에서 next(4)를 즉시 호출하지 않고 있다는 점에 주목하자. 제너레이터가 첫 yield를 만나면 다음 호출을 기다려주기 때문에 이를 나중에 해도 문제가 되지 않는다. 예를 들어, 다음과 같이 setTimeout 스케줄링 함수를 통해 호출하더라도 정상적으로 제너레이터가 이를 감지하고 작동한다.

// 1초 뒤 제너레이터가 다시 시작되어 4를 전달
setTimeout(() => generator.next(4), 1000);

이처럼 제너레이터 함수는 next/yield 키워드를 통해 서로 안팎으로 결과를 전달 및 교환할 수 있다. 또 하나의 예시를 통해 이 과정을 조금 더 살펴보자.

function* gen() {
  let ask1 = yield "2 + 2 = ?";
  console.log(ask1);	// 4
  
  let ask2 = yield "3 * 3 = ?";
  console.log(ask2);	// 9
}

let generator = gen();

console.log(generator.next().value);	// 2 + 2 = ?
console.log(generator.next(4).value);	// 3 * 3 = ?
console.log(generator.next(9).done);	// true

6) generator.throw

위 예시를 통해 살펴보았듯이 외부 코드는 yield의 결과가 될 값을 제너레이터 안에 전달하기도 한다. 이때 외부 코드가 에러를 만들거나 던질 수도 있는데, 에러 역시 결과의 한 종류이기 때문에 이는 자연스러운 현상이다.

에러를 yield 안으로 전달하려면 대신 generator.throw(err)를 호출해야 한다. generator.throw(err)를 호출하게 되면 erryield가 있는 줄로 던져진다.

function* gen() {
  try {
    let result = yield "2 + 2 = ?";
    
    console.log("위에서 에러가 던져지면 여기는 도달X");
  } catch (err) {
    console.log(err);
  }
}

let generator = gen();

let question = generator.next().value;

generator.throw(new Error('에러발생'));

마지막에서 두 번째 줄에서 generator.next() 메서드를 통해 yield"2 + 2 = ?" 값을 반환하고 다음 generator.next()를 만날때까지 대기하게 된다. 이때 generator.next()가 아닌 generator.throw(...)에서 에러를 던지기 때문에 함수 본문에 있는 yield 시점에서 에러가 발생하게 된다.

이때 에러는 함수 본문의 try...catch 문에 의해 catch 블록에서 에러가 잡히게 되고 콘솔에는 에러 관련 정보가 출력된다. 즉 try 블록에 있는 console.log에는 흐름이 도달하지 못한다.

만약 제너레이터 안에서 예외를 처리하지 않았다면 예외는 다른 경우와 마찬가지로 제너레이터 호출 코드인 외부로 떨어져 나오게 된다. 따라서 해당 에러가 외부 코드로 떨어져 나오는 시점에서도 에러 처리가 가능하다.

function* generate() {
  let result = yield "2 + 2 = ?";
}

let generator = generate();

let question = generator.next().value;

try {
  // 이 시점에서 에러가 던져지고
  // 제너레이터 함수 본문에서는 에러 처리 관련 코드가 없기에
  // 던져진 에러는 해당 영역으로 떨어져 나오게 됨
  generator.throw(new Error('에러발생'));
} catch (err) {
  // 위에서 떨어져 나온 에러를 잡을 수 있음
  console.log(err);
}

만약 여기서도 에러 처리 관련 설정을 해주지 않는다면, 다른 경우와 마찬가지로 스크립트가 죽게 될 것이다.

모던 자바스크립트에서는 제너레이터를 그렇게 많이 사용하지 않는다. 그러나 제너레이터를 사용하면 실행 중에도 제너레이터 호출 코드와 데이터를 교환할 수 있기 때문에 유용한 경우가 많다.

또한 제너레이터를 활용해서 구현된 라이브러리들도 종종 찾아볼 수 있다. 가장 대표적으로 Redux의 미들웨어인 redux-saga의 경우에는 제너레이터 문법을 통해 비동기 요청을 감지하고 그에 따른 응답을 적절하게 리턴하는 등으로 응용하고 있다. 관심이 있다면 공식문서에서 관련 문법과 그 쓰임을 살펴보자.

비동기 이터레이터와 제너레이터

비동기 이터레이터(async iterator)를 사용하면 비동기적으로 들어오는 데이터를 필요에 따라 처리할 수 있다. 예를 들어 네트워크를 통해 데이터가 여러 번에 걸쳐 들어오는 상황을 처리할 수 있다. 또한 제너레이터를 사용하여 이터레이터를 보다 간편하게 구현할 수 있는 것처럼, 비동기 제너레이터를 사용하면 이러한 데이터를 조금 더 간편하게 처리할 수 있다.

1) async 이터레이터

비동기 이터레이터는 일반 이터레이터와 거의 유사하며, 약간의 문법적 차이가 있다. 먼저 Iterable 객체 관련 코드를 다시 살펴보자.

let range = {
  from: 1,
  to: 5,
  
  [Symbol.iterator]() {
    return {
      current: this.from,
      last: this.to,
      
      next() {
        if (this.current <= this.last) {
          return { done: false, value: this.current++ }
        } else {
          return { done: true }
        }
      }
    }
  }
}

for(let value of range) {
  console.log(value);	// 1, 2, 3, 4, 5
}

위와 같은 이터러블 객체를 비동기적으로 만들려면 다음의 작업이 필요하다.

  • [Symbol.iterator] 대신 [Symbol.asyncIterator] 사용
  • next() 메서드는 프라미스 객체를 반환
  • 비동기 이터러블 객체를 대상으로 하는 반복 작업은 for await (let item of iterable) 문법 사용

위의 과정을 적용하여 일반 이터러블 객체를 비동기 이터러블 객체로 바꾸어보자.

let range = {
  from: 1,
  to: 5,
  
  [Symbol.asyncIterator]() {
    return {
      current: this.from,
      last: this.to,
      
      // async 키워드를 사용하여 간편하게
      // 프라미스 객체를 반환할 수 있도록 지정
      // 따라서 아래 리턴값은 자동으로 프라미스 객체로 감싸짐
      async next() {
        
        // async 영역이므로 await을 통한 비동기 처리 가능
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

(async () => {
  for await (let value of range) {
    console.log(value);	// 1, 2, 3, 4, 5 (1초 간격)
  }
})();

코드를 보면 알 수 있듯이 일반 이터러블 객체와 다른 점은 크게 없다. 앞서 언급한 점만 서로 다르다는 것을 알 수 있다. 비동기 이터러블 객체를 구현한 코드는 다음이 모두 구현되어 있다.

  1. [Symbol.asyncIterator] 메서드 구현
  2. 프라미스를 반환하는 next() 메서드 구현 - 이때 next() 메서드는 항상 async일 필요는 없다. 내부에서 Promise 객체를 반환하는 메서드라면 모두 가능하다.
  3. for await (let value of range)를 통해 순회

이때 전개문법(...)은 이터러블 객체에 적용할 수 있었지만 비동기 이터러블 객체에는 적용할 수 없다는 점에 유의하자. 전개문법은 비동기적으로 동작하지 않기 때문이다. 내부적으로 전개문법은 [Symbol.iterator] 프로퍼티가 있는지 찾게 되는데, [Symbol.asyncIterator] 프로퍼티는 이를 대체하는 속성이 아니기 때문에 에러가 발생한다.

console.log([...range]);	// TypeError

2) async 제너레이터

앞서 배운 바와 같이 제너레이터를 사용하면 이터러블 객체를 쉽게 구현할 수 있다. 이는 비동기 이터러블 객체 역시 동일하다. async 이터레이터 역시 async 제너레이터로 손쉽게 구현이 가능하다.

일반 제너레이터는 당연히 동기적으로 작동한다. 따라서 당연히 await 키워드를 통한 비동기 작업을 처리할 수 없다. 이를 가능케 하려면 비동기 제너레이터를 구현해야 한다. 이는 아주 간단하게 기존 제너레이터 함수에 async 키워드를 붙여줌으로써 구현할 수 있다.

async function* generateSequence(start, end) {
  for(let i = start; i <= end; i++) {
    // 1초 대기
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    yield 1;
  }
}

(async () => {
  let generator = generateSequence(1, 5);
  for await (let value of generator) {
    console.log(value);	// 1, 2, 3, 4, 5 (1초 간격)
  }
})();

위에서 보았던 비동기 이터레이터와 동일하게 for await...of 문법을 통해 비동기적으로 제너레이터를 사용할 수 있게 되었다. 이처럼 간단하게 async 키워드를 붙여주는 것만으로도 매우 간단하게 비동기 제너레이터를 만들 수 있다.

async 제너레이터의 동작은 모두 비동기적이다. 따라서 generator.next() 메서드를 호출할 때 역시 비동기적으로 처리해야 한다. 비동기 제너레이터에서의 next() 메서드는 프라미스를 반환하기 때문이다.

// 비동기 제너레이터라면
let result = generator.next();		// (X)
let result = await generator.next();	// (O)

3) async 이터러블

반복 가능한 객체인 이터러블 객체는 내부에 [Symbol.iterator]가 구현되어 있는 객체이다. 이때 제너레이터를 사용한다면 [Symbol.iterator] 함수 내부에 next() 메서드를 구현하지 않고, 바로 제너레이터를 반환하도록 구현하여 동일하게 이터러블 객체를 만들 수 있다.

let range = {
  from: 1,
  to: 5,
  
  // [Symbol.iterator]: function*() 를 축약한 문법
  *[Symbol.iterator]() { 
    for(let value = this.from; value <= this.to; value++) 
      yield value;
  }
};

for(let value of range) 
  console.log(value);	// 1, 2, 3, 4, 5

물론 비동기 동작을 추가하려면 [Symbol.iterator]async *[Symbol.asyncIterator]로 바꾸어주면 된다.

let rane = {
  from: 1,
  to: 5,
  
  async *[Symbol.asyncIterator]() {
    for(let value = this.from; value <= this.to; value++) {
      await new Promise(resolve => setTimeout(resolve, 1000));
      yield value;
    }
  }
};

(async () => {
  for await (let value of range) {
    console.log(value);
  }
})();
    

4) 실제사례 적용

지금까지는 매우 간단한 예시를 통해 제너레이터와 비동기 제너레이터의 쓰임을 살펴보았다. 이젠 실무에서도 접할 법한 유스케이스를 간단하게 다듬어 어떻게 제너레이터를 적용할 수 있을지 살펴보도록 하자.

상당히 많은 온라인 서비스가 페이지네이션(pagination)을 구현해 데이터를 전송한다. 서버에 요청한 사용자 목록 리스트가 대단히 많을 경우에는 일정 숫자 단위로 사용자를 끊어서 한 페이지로 구성하고, 다음 페이지를 볼 수 있는 URL과 함께 응답하는 등의 기법으로 페이지네이션을 구현할 수 있다.

이런 패턴은 다양한 서비스에서 찾아볼 수 있는데, 예시로 들 깃허브(GitHub)에서 커밋 이력을 볼 때 역시 페이지네이션을 사용한다.

  • 클라이언트는 https://api.github.com/repos/<repo>/commits의 URL로 요청을 전송
  • 깃허브에서는 커밋 30개 단위로 정보가 담긴 JSON 객체와 함께, 다음 페이지에 대한 정보를 Link 헤더에 담아 응답
  • 더 많은 커밋 정보가 필요하면 헤더에 담긴 링크를 사용해 다음 요청 전송

실제 깃허브 API는 이보다 더 복잡하지만, 커밋 정보가 담긴 이터러블 객체를 만들어서 아래와 같이 객체를 대상으로 반복 작업을 할 수 있게 해주는 간단한 API를 구현해보자.

// 커밋 정보를 얻어올 GitHub 리포지토리
let repo = 'javascript-tutorial/en.javascript.info'; 

for await (let commit of fetchCommits(repo)) {
  // 여기서 각 커밋을 처리
}

필요할 때마다 요청을 보내 커밋 정보를 가져오는 함수를 fetchCommits(repo)라고 하자. 해당 함수에서 페이지네이션 관련 일들을 모두 처리하도록 하면, 원하는대로 for await...of 문에서 각 커밋을 처리할 수 있을 것이다. 이 같은 처리를 비동기 제너레이터를 통해 쉽게 구현할 수 있다.

async function* fetchCommits(repo) {
  let url = `https://api.github.com/repos/${repo}/commits`;
  
  while (url) {
    const response = await fetch(url, {
      // GitHub는 모든 요청에 user-agent헤더를 강제
      headers: {'User-Agent': 'Our Script'},
    });
    
    const body = await response.json();
    
    // 헤더에 담긴 다음 페이지를 나타내는 URL을 정규식을 통해 추출
    let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/)
    nextPage = nextPage?.[1];
    
    url = nextPage;
    
    for(let commit of body) {
      // async 제너레이터이기 때문에 최종적으로
      // 반환되는 결과가 프라미스 객체에 감싸져서 반환
      yield commit;
    }
  }
}

위의 정규식 표현은 아직 다루지는 않았지만, 다음의 과정을 통해 헤더에서https://api.github.com/repositories/93253246/commits?page=2와 같은 URL만 추출하기 위한 과정이라고 생각하면 좋다.

while (url)을 통해 계속 다음 페이지네이션이 헤더에 있을 경우 반복해서 커밋 정보를 가져오게 된다. 물론 yield가 걸려있기 때문에 먼저 해당 페이지네이션에 대한 커밋 이력을 모두 가져오고 나서 done: true가 반환되면 반복문이 트리거되어 다음 페이지네이션을 가져오는 구조가 된다. 이를 통해 원하는 만큼의 커밋 내역을 페이지네이션 단위에 맞추어 서버에 연달아 요청이 가능하고, 이를 통해 서버에게 전달되는 과부화를 어느정도 방지할 수 있다.

const repo = 'javascript-tutorial/en.javascript.info';

(async () => {
  let count = 0;
  
  for await (const commit of fetchCommits(repo)) {
    console.log(commit.author.login);
    
    if(++count === 100) break;	// 100번째 커밋에서 중단
  }
})();
    

웹 개발을 하다보면 띄엄띄엄 들어오는 데이터 스트림을 다뤄야 하는 경우가 종종 생기기 마련이다. 용량이 큰 파일을 다운로드 하거나, 업로드 하는 경우가 그러한 경우이다. 이런 데이터를 처리할 때 async 제너레이터를 사용할 수 있을 것이다.

References

  1. https://ko.javascript.info/generators-iterators
profile
개발잘하고싶다

0개의 댓글