Generator and yield

2 minute read

Generator and yield

개요

  • async/await가 있지만 generator/yield를 쓰는 게 나은 거 같은 경우가 있어서 정리
  • 가령 터미널로 명령어를 계속 입력 받고 싶은 경우,
    • 여러 예제에서는 재귀적으로 입력값을 읽어들이는 함수를 계속 실행하게 한다. 하지만 그러면 결국 콜 스택이 계속 쌓일 텐데, 그게 싫어서 다른 방법을 찾아보다 보니 generator 예제가 있어서 테스트
    • generatorreadline.question() 발생 시키고 그때마다 yield로 결과를 반환

Generator?

  • generator function의 인스턴스

관련 파일

  • src/parsing/parser.cc: 말 그대로 프로그램을 파싱하는 소스코드
  • src/interpreter/bytecode-generator.cc: 바이트코드 생성 소스 코드
  • src/objects/js-generator.h: Generator 클래스 정의한 헤더 파일

async 함수와의 관계?

문법

function* generator(i) {
  yield i;
  yield i + 10;
}

const gen = generator(10); // generator는 constructor(즉 new 키워드)로 생성되지 않는다

console.log(gen.next().value);
// expected output: 10

console.log(gen.next().value);
// expected output: 20

yield의 동작 방식?

  • 마치 소켓과 같은 역할을 한다
    • yield 키워드 위치의 결과를 generator.next() 호출한 곳으로 반환한다
    • generator.next(파라미터)처럼 어떤 파라미터가 있으면 해당 파라미터를 받아와서 변수에 할당할 수 있다

터미널 입력 계속 받기

// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/yield
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator

import readline from "readline";

async function* questions() {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  let query;
  try {
    while (true) {
      query = yield new Promise((resolve) => {
        if (query) {
          rl.question(query, resolve); // readline으로 입력된 값을 resolve해서 반환
        } else {
          resolve(null); // 처음 yield로 이동 시(generator.next();)에 query가 비어 있으므로 null로 resolve
        }
      });
    }
  } finally {
    rl.close();
  }
}

async function run() {
  let loopCnt = 0;
  const generator = questions();
  // Generator 오브젝트 생성 시 초기 파라미터를 넘기는 경우와 넘기지 않는 경우가 있다
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*#passing_arguments_into_generators
  generator.next(); // 초기 파라미터 넘기지 않으면, `next()`사용해서 초기 yield까지 한번 이동하게 해야 한다
  let generated = await generator.next(`${loopCnt++}번째 질문\n`);
  console.log("Outside of while, first generated", generated);
  while (generated.done === false) {
    generated = await generator.next(`${loopCnt++}번째 질문\n`);
    console.log("Inside of while", generated);
    if (loopCnt == 3) {
      generator.return();
      break;
    }
  }
}

run();
/*
0번째 질문
안녕
Outside of while, first generated { value: '안녕', done: false }
1번째 질문
Hello
Inside of while { value: 'Hello', done: false }
2번째 질문
World
Inside of while { value: 'World', done: false }
*/

기타

참고 링크

Updated: