자바스크립트에서 함수 실행을 멈추는 법이 있다?

BeomDev·2023년 11월 2일
0

JavaScript

목록 보기
1/1
post-thumbnail

제너레이터 함수

함수를 멈췄다 실행시켰다 할 수 있는 제동장치를 가진 특수한 함수이다.
* 키워드를 통해 제너레이터 함수를 정의할 수 있고, 함수 실행을 멈추기 위한 브레이크는 yield 키워드를 통해 잡을 수 있다.

function* gen () {
  // 여기 코드 실행 되고
  yield '브레이크 잡고';
  // 또 여기 코드 실행 되고
  yield '브레이크 또 잡고';
  // 또또 여기 코드 실행 되고
  yield '브레이크 또또 잡고';
}

const generator = gen();

generator.next().value // '브레이크 잡고';
generator.next().value // '브레이크 또 잡고';
generator.next().value // '브레이크 또또 잡고';

풀어서 설명하자면, 제어권을 yield에게 넘기는 것이다.

더 쉽게 말하자면 운전은 함수가 하는데, 함수의 브레이크를 yield가 가지고 있는것이다.

보통 일반적으로 사용하는 함수들은 함수가 실행되는 시점에 제어권을 함수에게 넘기게 된다.

하지만 제네레이터 함수로 선언하고 내부에 yield 키워드를 통해 브레이크를 잡으면 함수의 실행이 잠시 멈추게 되고 next()를 통해 액셀을 밟으면 이전에 멈췄던 위치에서 실행을 재개하게 된다.

제너레이터 함수의 반환 값

제너레이터 함수가 반환하는 값은 '이터레이터'이다. 이를 '제너레이터 객체'라고 부른다.

이터레이터란?
이터러블 프로토콜을 준수하는 객체가 프로토타입으로 가지고 있는 Symbol.iterator 메소드를 실행함으로서 반환되는 객체
이터레이터 객체는 아래의 형태를 가진다.

{
	value: any;
    done: Boolean;
}
const iter = [1,2,3][Symbol.iterator]();
iter.next() // {value: 1, done: false}
iter.next() // {value: 2, done: false}
iter.next() // {value: 3, done: false}
iter.next() // {value: undefined, done: true}

이터레이터

제너레이터가 반환하는 이터레이터 객체는 일반 이터레이터 객체와는 차이가 있는데, 바로 next 메소드의 파라미터로 값을 전달할 수 있다는 것이다.

함수 실행 중에 외부에서 함수의 흐름에 관여할 수 있다는 의미가 된다.

function* gen() {
  // [1] 질문을 제너레이터 외부에 던지고 기다린다.
  const question1 = yield '1 + 1은?';
  // [2] 외부에서 next의 인자로 전달된 값을 받아 처리
  console.log(question1);
  const question2 = yield '8 + 8은?';
  console.log(question2);
}

const generator = gen();

generator.next().value; // [1] yield는 value를 반환
// [1] '1 + 1은?'

generator.next('창문').value; // [2] 결과를 제너레이터 내부로 전달
// [2] '창문'
// '8 + 8은?'

generator.next('올림픽').done;
// '올림픽'
// true

주석을 보고 코드 흐름을 이해해보면, yield는 질문을 던지고 외부의 답변을 기다리고, next의 인자로 값이 전달되면 받아 처리할 수 있게 된다.

에러처리

방금 예시에서 봤듯이 제네레이터는 next의 전달되는 인자를 통해서 제너레이터 내부에서 처리하기도 하는데, 만약 외부의 코드가 에러를 만들어야 하는 값이라면 어떻게 해야할까?

generator.throw() 메소드를 통해 yield에 에러를 전달할 수 있다.

function* generatorFunc() {
  try {
    yield '에러를 일으키는 값';
  } catch (e) {
    console.log(e);
  }
}

const generator = generatorFunc();

const { value } = generator.next();

if (value === '에러를 일으키는 값') {
  generator.throw(new Error('오류 발생'));
}

외부에서 상태 확인

제너레이터를 통해 외부에서 상태를 확인하는 코드를 실제 예제를 통해 확인해보자.

import GameManger from './gameManager'
import { Random } from '@woowacourse/mission-utils';

class RacingGame {
  #racingCars = [];
  
  constructor(carsNameInput) {
    carsNameInput.split(',').forEach((carName) => {
      this.#racingCars.push(new Car(carName.trim()));
    });
  }

  play(tryTime) {
    for (let i = 0; i < tryTime; i += 1) {
      this.#moveForwardRandomly();
      GameManager.printStatus(this.#racingCars);
    }
  }

  #moveForwardRandomly() {
    this.#racingCars.forEach((car) => {
      const canGoForward = Random.pickNumberInRange(0, 9) >= 4;

      if (canGoForward) {
        car.moveForward();
      }
    });
  }
}

export default RacingGame;

RacingGame 클래스는 생성자를 통해 자동차 이름에 대한 입력값을 받아서 비공개 멤버인 racingCars에게 Car 인스턴스를 push 해주고 있다.

play 메소드를 한 번 들여다보자.

play(tryTime) {
  for (let i = 0; i < tryTime; i += 1) {
    this.#moveForwardRandomly();
    GameManager.printStatus(this.#racingCars);
  }
}

play 메서드는 시도할 횟수에 대한 입력값을 받아, 시도 횟수만큼 Car 인스턴스를 앞으로 전진시키는 moveForwardRandomly 비공개 메소드와 GameManagerprintStatus 메소드에 클래스 내부 상태를 전달해 실행시키고 있다.

play 메서드가 RacingGame 클래스 내부의 상태를 인자로 받아 처리하는 것은 RacingGame 클래스가 출력 책임을 가지고 있다고도 볼 수 있겠다.

결국, 하나의 클래스가 자동차를 앞으로 전진시키는 책임, 또 게임 상태를 콘솔에 출력하는 두 가지 책임을 가지고 있다.

객체지향 프로그래밍의 단일책임원칙(SRP)을 위배한 케이스인 것이다.

단일책임의 원칙을 지키기 위해서는 반복문이 한 번 실행될 때마다 현재 클래스의 상태를 외부로 전달해 주어야 한다.

그렇다면 진행되고 있는 반복문 내부에서 프로그래머는 어떻게 반복문이 1회 반복할 때마다 정지시켜 자동차 인스턴스에 대한 상태를 조회할 것인가?

이 때 제너레이터 함수를 사용할 수 있다.

* play(tryTime) {
  for (let i = 0; i < tryTime; i += 1) {
    this.#moveForwardRandomly();
    yield this.#racingCars;
  }
}

* 키워드를 통해 play 메소드를 제너레이터 함수로 선언해준 뒤, 자동차 인스턴스를 담고 있는 배열을 yield를 통해 반환해주면 반복문 중간에 외부에서 제너레이터 객체를 통해 반복문 내부의 상태를 확인할 수 있게된다.

const racingGame = new RacingGame('js, py, algo');
const game = racingGame.play(4);
let status = game.next();

while (!status.done) {
  GameManager.printStatus(status.value);
  status = game.next();
}

0개의 댓글