Skip to content

Latest commit

 

History

History
241 lines (181 loc) · 6.2 KB

Generator와 async-await.md

File metadata and controls

241 lines (181 loc) · 6.2 KB

Generator와 async-await

GeneratorES6에서 도입되었으며 Iterable을 생성하는 함수다. Generator를 사용하면 Iteration Protocol을 사용하여 Iterable을 생성하는 방식보다 간편하다.
Iteration Protocol에 대한 자세한 내용은 다음을 참고하길 바란다.


Generator 함수의 정의

Generator 함수는 코드 블록을 한 번에 실행하지 않고 함수 코드 블록의 실행을 중지했다가 필요한 시점에 다시 시작할 수 있는 함수다.
Generator 함수는 function * 키워드를 사용하며 하나 이상의 yield 문을 포함한다.

// 함수 선언문
function* decFunc() {
  yield 1;
}

let genObj = decFunc();

// 함수 표현식
const expFunc = function* () {
  yield 1;
};

genObj = expFunc();

// 메서드
const obj = {
  * objectMethod() {
    yield 1;
  }
};

genObj = obj.objectMethod();

// 클래스 메서드
class GenClass {
  * classMethod() {
    yield 1;
  }
}

const genClass = new GenClass();
genObj = genClass.classMethod();

Generator 객체

Generator 함수를 호출하면 코드 블록이 실행되는 것이 아니라 Generator 객체를 반환한다. Generator 객체는 이터러블이면서 동시에 이터레이터다. 따라서 Symbol.iterator를 사용하여 이터레이터를 생성할 필요 없다.

function* counter() {
  console.log('First');
  yield 1;
  console.log('Second');
  yield 2;
  console.log('Third');
  yield 3;
  console.log('The end');
}

const genObj = counter();

console.log(genObj.next()) //{value: 1, done: false}
console.log(genObj.next()) //{value: 2, done: false}
console.log(genObj.next()) //{value: 3, done: false}
console.log(genObj.next()) //{value: undefined, done: true}

Generator 객체는 이터러블이면서 이터레이터이기 때문에 next()메서드를 가지고 있다. 따라서 next() 메서드를 호출하면 yield문까지 실행되고 일시 중지된다. 다시 next() 메서드를 호출하면 다음 yield문을 만날 때까지 실행된 뒤 일시 중지된다. 위의 코드에서는 next()메서드를 호출하면 value값으로 yield문의 오른쪽에 선언된 값이 반환된다.


Generator 객체를 이용한 이터러블 구현

const genObj = (function* () {
  let i = 0;

  while(true) {
    yield ++i;
  }
}());

for (let item of genObj) {
  if (item === 10) break;
  console.log(item);
}
// Generator 함수에 파라미터 전달
const genObj = function* (max) {
  let i = 0;

  while(true) {
    if (i === max) break;
    yield ++i;
  }
}

for (let item of genObj(10)) {
  console.log(item);
}
// next 메서드에 파라미터 전달
function* genFunc(n) {
  let res;
  res = yield n;

  console.log(res);
  res = yield res;

  console.log(res);
  res = yield res;

  console.log(res);
  return res;
}
const genObj = genFunc(0);

console.log(genObj.next());
console.log(genObj.next(1));
console.log(genObj.next(2));
console.log(genObj.next(3));

Generator를 이용한 비동기 처리

Generator의 진면목은 비동기 프로그래밍에서 볼 수 있다. 함수가 실행 도중에 멈춘다니. 언제 응답이 올 지 알 수 없기 때문에, callback을 등록하는 비동기 프로그래밍에 응용하면 callback hell을 탈출할 수 있지 않을까?

function getId(phoneNumber) {
    // …
    iterator.next(result);
}

function getEmail(id) {
    // …
    iterator.next(result);
}

function getName(email) {
    // …
    iterator.next(result);
}

function order(name, menu) {
    // …
    iterator.next(result);
}
function* orderCoffee(phoneNumber) {
    const id = yield getId(phoneNumber);
    const email = yield getEmail(id);
    const name = yield getName(email);
    const result = yield order(name, 'coffee');
    return result;
}

const iterator = orderCoffee('010-1234-1234');
iterator.next();

async-await를 이용한 비동기 처리

async-await를 활용하면 Generator를 이용했을 때 처럼 제어권을 다시 Generator에 넘기기 위해 각 비동기 함수마다 next()를 호출하지 않아도 된다.

async function orderCoffee(phoneNumber) {
    const id = await getId(phoneNumber);
    const email = await getEmail(id);
    const name = await getName(email);
    return await order(name, 'coffee')
}

orderCoffee('011-1234-5678').then(result => {
    console.log(result);
});

Generator는 어떻게 구현되어 있을까?

// ES6
function* foo(){
    yield bar();
}

// ES5 Compiled
"use strict";

var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);

function foo() {
    return regeneratorRuntime.wrap(
        function foo$(_context) {
            while (1) {
                switch ((_context.prev = _context.next)) {
                    case 0:
                        _context.next = 2;
                        return bar();
                    case 2:
                    case "end":
                        return _context.stop();
                }
            }
        },
        _marked, this
    );
}

Genrator는 결국 iterable Protocol를 구현하는 객체이다. 그러나 프로토콜과 관련된 어느것도 보이지 않는다.

대신 regeneratorRuntime이 보인다.

babel에서는 regeneratorRuntime라이브러리를 사용해서 구현을 했다.

코드의 역사를 따라가다 보면 facebook/regenerator repository에 도달하게 된다.

이 라이브러리는 2013년 Node.js v0.11.2에서 generator syntax를 지원하기 위해 만들어 졌으며, Babel에서는 이 라이브러리를 사용하여 generator를 구현하고 있다. 실제 코드를 들여다보면 Symbol과 Iterator를 이용해서 Iterable Protocol을 구현하고 있다.


Reference