JavaScript 오답노트 : 2. generator, yield, for await of

샤워실의 바보·2023년 11월 3일
0
post-thumbnail

JavaScript의 제너레이터(generator)는 function* 키워드를 사용해 정의하는 특별한 종류의 함수입니다. 제너레이터 함수는 호출될 때 즉시 실행되지 않고, 대신에 함수 실행을 나타내는 반복자(iterator)를 반환합니다. 이 반복자는 next 메소드를 통해 제너레이터 함수의 실행을 진행할 수 있습니다. yield 키워드는 제너레이터 함수의 실행을 일시 중지시키고, 호출자에게 제어권을 넘겨줍니다.

제너레이터는 반복 가능(iterable)하며, for...of 루프를 통해 사용될 수 있습니다. 또한 제너레이터는 복잡한 비동기 코드를 쉽게 처리하는 데 사용될 수 있는데, 이는 yield를 통해 실행을 중지하고 외부에서 다시 시작할 수 있기 때문입니다.

간단한 제너레이터 함수의 예제는 다음과 같습니다:

function* generatorFunction() {
  console.log('이전 처리');
  yield 'Hello';
  console.log('이후 처리');
  yield 'World';
}

const generator = generatorFunction(); // 제너레이터 객체를 얻음

console.log(generator.next().value); // '이전 처리'를 출력하고, 'Hello'를 반환
console.log(generator.next().value); // '이후 처리'를 출력하고, 'World'를 반환
console.log(generator.next().value); // 더 이상 반환할 값이 없으므로 undefined 반환

위 코드를 실행하면 다음과 같은 출력을 볼 수 있습니다:

이전 처리
Hello
이후 처리
World
undefined

제너레이터 함수는 일시 중지 및 재개가 가능하여 복잡한 로직을 다루는 데 매우 유용합니다. 예를 들어, 비동기 작업을 순차적으로 실행하는 데에도 사용될 수 있습니다. yield는 프로미스를 반환하고, 제너레이터 외부에서 이 프로미스가 완료될 때까지 기다린 후 다음 next 호출로 결과를 제너레이터에 다시 전달하는 패턴을 구성할 수 있습니다. 이러한 패턴은 비동기 작업을 마치 동기적으로 작동하는 것처럼 보이게 만들어, 코드의 가독성을 향상시킬 수 있습니다.

이전에는 이러한 패턴을 co 라이브러리 같은 것을 사용하여 구현했지만, 현대의 JavaScript는 async/await를 통해 이러한 기능을 훨씬 쉽게 구현할 수 있도록 지원합니다. 그럼에도 불구하고, 제너레이터는 특정 시나리오에서 여전히 유용하게 사용될 수 있습니다.

JavaScript의 Generatoryield 그리고 Dart의 Streamyield는 비동기 프로그래밍에서 데이터의 흐름을 제어하는 데 각각 사용됩니다. 이들은 데이터 스트림을 손쉽게 생성하고 관리할 수 있도록 해 주며, 이는 'Lazy evaluation' 또는 'Pull-based' 접근법을 따릅니다. 즉, 필요할 때만 데이터를 처리하고 생성하는 것이죠.

JavaScript에서 Generator 함수는 function* 키워드로 정의되며, 내부에서 yield를 사용하여 연속적인 값들을 하나씩 '생산'할 수 있습니다. yield는 제너레이터 함수의 실행을 일시 중지하고, 호출자에게 제어권을 넘긴 다음, 외부에서 다시 next()를 호출하면 그 시점에서부터 실행을 재개합니다. 이는 데이터를 생산하는 속도를 소비하는 속도에 맞출 수 있게 하며, 특히 복잡한 계산이나 IO 작업이 연속적으로 이루어져야 할 때 유용합니다.

Dart에서는 async* 함수를 정의할 때 Stream을 사용하고, yield를 통해 스트림에 값을 방출할 수 있습니다. Dart의 Stream은 여러 개의 비동기 이벤트를 전달하는 방법을 제공합니다. yield를 사용함으로써 함수 내부에서 비동기 이벤트를 순차적으로 추가하고, 스트림 구독자는 이벤트가 방출될 때마다 이를 수신할 수 있습니다. 이는 또한 'back pressure'를 처리하고, 이벤트 또는 데이터의 흐름을 제어할 수 있는 방법을 제공합니다.

두 언어 모두 yield를 사용하여 생성된 시퀀스나 데이터 스트림은 '고정되지 않은' 데이터 구조를 가지고 있어, 무한한 시퀀스를 모델링하거나 큰 컬렉션을 메모리에 한 번에 적재하지 않고도 처리할 수 있게 해줍니다. 이런 점에서 GeneratorStream은 모두 비동기 프로그래밍에서 매우 강력한 추상화를 제공합니다.

요약하자면, JavaScript의 Generator와 Dart의 Stream은 비동기 데이터 흐름을 제어하는 유사한 패턴으로, 개발자에게 보다 세련된 방식으로 데이터를 생산하고 소비할 수 있는 기능을 제공합니다. 이러한 구조는 특히 웹 애플리케이션에서의 비동기 이벤트 처리, 데이터 스트리밍, 복잡한 비동기 로직의 관리에 있어 매우 유용하게 사용됩니다.

JavaScript에서 비동기 프로그래밍은 매우 일반적인 작업입니다. 이를 효과적으로 관리하기 위해 제너레이터(Generators), for await...of 루프, 그리고 async/await 같은 구문이 사용됩니다. 이들을 함께 사용하면 비동기 코드를 마치 동기 코드처럼 쉽게 읽고 작성할 수 있습니다.

제너레이터(Generators)

제너레이터는 function* 선언을 통해 정의되는 특별한 함수로, yield 키워드를 사용하여 함수의 실행을 일시 중지하거나 값을 외부로 전달할 수 있습니다. 제너레이터 함수는 호출될 때 바로 실행되지 않고, 대신 Generator 객체를 반환합니다. 이 객체는 next() 메소드를 통해 제너레이터 함수의 실행을 제어할 수 있습니다.

for await...of

for await...of 루프는 비동기 이터러블(예: 비동기 작업의 시퀀스를 나타내는 AsyncIterable 객체 또는 프로미스를 반환하는 제너레이터 함수)을 반복하기 위해 설계되었습니다. 이 루프는 각 이터레이션에서 비동기적으로 반환되는 값을 기다리고(await), 해당 값이 준비되면 반복 루프의 다음 주기로 진행합니다.

비동기 프로그래밍과 연관지어서

async/await은 프로미스 기반의 비동기 코드를 동기적으로 보이게 하는 문법적 설탕입니다. await 키워드는 프로미스의 해결을 기다린 후, 해결된 값을 반환합니다.

이러한 기능들을 연관지어 사용하면 다음과 같은 방식으로 복잡한 비동기 로직을 처리할 수 있습니다:

  1. 비동기 작업의 시퀀스 생성: 제너레이터 함수 내에서 비동기 작업을 수행하고, 각 작업의 결과를 yield합니다. 이때 각 yield 표현식은 프로미스를 반환하도록 합니다.

  2. 비동기 시퀀스의 소비: for await...of 루프를 사용하여 제너레이터 함수에 의해 생성된 비동기 시퀀스를 소비합니다. 이 루프는 내부적으로 next() 호출 사이에 await를 수행하여, 각 비동기 작업이 완료될 때까지 기다립니다.

이렇게 함으로써, 비동기 코드의 흐름을 마치 동기 코드를 작성하듯 자연스럽게 만들 수 있습니다. 예를 들어, 여러 웹 API를 순차적으로 호출하고 각각의 응답을 처리하는 시나리오에서 매우 유용합니다.

여기에 코드 예시가 있습니다:

async function* asyncGenerator() {
  const urls = ['url1', 'url2', 'url3']; // 예시 URL들
  for (const url of urls) {
    yield fetch(url); // fetch는 Promise를 반환하는 비동기 함수입니다.
  }
}

// asyncGenerator를 소비하는 비동기 루프
async function consumeAsyncSequence() {
  for await (const response of asyncGenerator()) {
    const data = await response.json(); // 각 응답의 JSON을 기다립니다.
    console.log(data); // 데이터를 처리합니다.
  }
}

consumeAsyncSequence();

이 코드에서 asyncGenerator는 여러 비동기 작업(여기서는 fetch 호출)을 나타내며, consumeAsyncSequence 함수는 for await...of 루프를 통해 이러한 비동기 작업들을 순차적으로 소비하고 결과를 처리합니다.

profile
공부하는 개발자

0개의 댓글