Front-end/JavaScript

[JavaScript] async 이터레이터와 제너레이터

Nave 2022. 5. 31. 13:15

비동기 이터레이터를 사용하면 비동기적으로 들어오는 데이터를 필요에 따라 처리할 수 있다. 네트워크를 통해 데이터가 여러 번에 걸쳐 들어오는 상황을 처리할 수 있기 때문. 

더보기

1. async 이터레이터

비동기 이터레이터는 일반 이터레이터와 유사하며, 약간의 문법적인 차이가 있다.

let range = {
  from: 1,
  to: 5,

  // for..of 최초 실행 시, Symbol.iterator을 호출해야함
  [Symbol.iterator]() {
    // Symbol.iterator메서드는 이터레이터 객체를 반환
    // 이후 for..of는 반환된 이터레이터 객체만을 대상으로 동작하는데,
    // 다음 값은 next()에서 정해진다.
    return {
      current: this.from,
      last: this.to,

      // for..of 반복문에 의해 각 이터레이션마다 next()가 호출됨
      next() { // (2)
        //  next()는 객체 형태의 값, {done:.., value :...}을 반환
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

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

-> 1부터 5까지 연속된 수를 반환하는 이터레이터

이 이터러블 객체를 비동기적으로 만드려면

  • Symbol.iterator 대신 Symbol.asyncIterator를 사용해야한다
  • next()는 프라미스를 반환해야 한다.
  • 비동기 이터러블 객체를 대상으로 하는 반복 작업은 for await (let item of iterable) 반복문을 사용해 처리해야 한다.
//위의 예시를 토대로 일초마다 비동기적으로 값을 반환하는 이터러블 객체

let range = {
  from: 1,
  to: 5,

  // for await..of 최초 실행 시, Symbol.asyncIterator를 호출해야함
  [Symbol.asyncIterator]() { // (1)
    // Symbol.asyncIterator 메서드는 이터레이터 객체를 반환
    // 이후 for await..of는 반환된 이터레이터 객체만을 대상으로 동작하는데,
    // 다음 값은 next()에서 정해집니다.
    return {
      current: this.from,
      last: this.to,

      // for await..of 반복문에 의해 각 이터레이션마다 next()가 호출된다
      async next() { // (2)
        //  next()는 객체 형태의 값, {done:.., value :...}를 반환
        // (객체는 async에 의해 자동으로 프라미스로 감싸진다.)

        // 비동기로 무언가를 하기 위해 await를 사용할 수 있다.
        await new Promise(resolve => setTimeout(resolve, 1000)); // (3)

        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

(async () => {

  for await (let value of range) { // (4)
    alert(value); // 1,2,3,4,5
  }

})()

--> async 이터레이터와 일반 이터레이터의 차이점

  • 객체를 비동기적으로 반복 가능하도록 하려면, Symbol.asyncIterator메서드가 구현되어 있어야한다.
  • Symbol.asyncIterator는 프라미스를 반환하는 메서드인 next()가 구현된 객체를 반환해야한다.
  • next()는 async 메서드일 필요는 없는데, 프라미스를 반환하는 메서드라면 일반 메서드도 괜찮다. 근데 여기서는 async를 사용하면 await도 사용할 수 있기 때문에, 편의상 async메서드를 사용해 일 초의 딜레이가 생기도록  했음
  • 반복작업을 하려면 ‘for’ 뒤에 'await’를 붙인 for await(let value of range)를 사용하면 된다. 

2. async 제너레이터

//start부터 end까지의 연속된 숫자를 생성해주는 제너레이터 

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

for(let value of generateSequence(1, 5)) {
  alert(value); // 1, then 2, then 3, then 4, then 5
}

이 제러레이터를 async 제너레이터로 변형하면

async function* generateSequence(start, end) {

  for (let i = start; i <= end; i++) {

    await new Promise(resolve => setTimeout(resolve, 1000));

    yield i;
  }

}

(async () => {

  let generator = generateSequence(1, 5);
  for await (let value of generator) {
    alert(value); // 1, 2, 3, 4, 5
  }

})();

--> async 키워드를 붙이기만 하면 제너레이터 안에서 프라미스와 기타 async 함수를 기반으로 동작하는 await를 사용할 수 있다.

async 제너레이터의 generator.next() 메서드는 비동기적이 되고, 프라미스를 반환한다는 점은 일반 제너레이터와 async 제너레이터엔 또 다른 차이이다.

더보기

3. async 이터러블

반복 가능한 객체를 만드려면 객체에 Symbol.iterator을 추가해야 한다.

let range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    return <range를 반복가능하게 만드는 next가 구현된 객체>
  }
}


//요런 식으로

그런데 Symbol.iterator는 위 예시와 같이 next가 구현된 일반 객체를 반환하는 것 보다, 제너레이터를 반환하도록 구현하는 경우가 더 많다.

let range = {
  from: 1,
  to: 5,

  *[Symbol.iterator]() { // [Symbol.iterator]: function*()를 짧게 줄임
    for(let value = this.from; value <= this.to; value++) {
      yield value;
    }
  }
};

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