Generator

Siwoo Pak·2021년 7월 14일
0

Javascript

목록 보기
19/34

Generator

  • 우리가 직접 iterable인 객체를 만들 수는 없을까요?
  • iterable protocol을 구현하기만 하면 어떤 객체든 iterable이 될 수 있음
  • iterable을 구현하는 가장 쉬운 방법은 ES2015에 도입된 generator()를 사용하는 것
  • Generator()는 iterable 객체를 반환하는 특별한 형태의 함수.
// generator() define
function* gen1() {...}
// 표현식으로 사용하기
const gen2 = function* () {...}
// 메소드 문법으로 사용하기
const obj = {
  * gen3() {...}
}
  • generator()를 호출하면 generator객체가 생성되는데, 이 객체는 iterable protocol을 만족함. 즉 Symbol.iterator 속성을 갖고 있다.
function* gen1() {
  // ...
}

// `gen1`를 호출하면 iterable이 반환됩니다.
const iterable = gen1();

iterable[Symbol.iterator]; // [Function]
  • generator() 안에서는 'yield'라는 특별한 키워드 사용가능
  • generator() 안에서 'yield'키워드는 'return'과 유사한 역할을 함.
  • iterable의 기능을 사용할 때 'yield'키워드 뒤에 있는 값들을 순서대로 반환해 줌.
function* numberGen() {
  yield 1;
  yield 2;
  yield 3;
}
// 1, 2, 3이 순서대로 출력됩니다.
for (let n of numberGen()) {
  console.log(n);
}
  • yield* 표현식을 사용하면, 다른 generator() 넘겨준 값을 대신 넘겨줄 수 있음.
function* numberGen() {
  yield 1;
  yield 2;
  yield 3;
}

function* numberGen2() {
  yield* numberGen();
  yield* numberGen();
}

// 1, 2, 3, 1, 2, 3이 순서대로 출력됩니다.
for (let n of numberGen2()) {
  console.log(n);
}
  • genenerator()는 일반적인 함수의 내부 동작 방식과 별차이가 없습니다. 차이점 yield 키워드뿐.
  • 다음은 generator()를 이용한 예들
// 등차수열 생성하기
function* range(start = 0, end = Infinity, step = 1) {
  for (let i = start; i < end; i += step) {
    yield i;
  }
}

// 피보나치 수열 생성하기
function* fibonacci(count = Infinity) {
  let x = 1;
  let y = 1;
  for (let i = 0; i < count; i++) {
    yield x;
    [x, y] = [y, x + y];
  }
}

// 하나의 항목을 계속 넘겨주기
function* repeat(item, count = Infinity) {
  for (let i = 0; i < count; i++) {
    yield item;
  }
}

// 여러 요소를 반복해서 넘겨주기
function* repeatMany(array) {
  while (true) {
    for (let item of array) {
      yield item;
    }
  }
}

generator() 사용시 주의할 점

  • generator()로부터 생성된 iterable은 한 번만 사용가능
  • generator() 내부에서 정의된 일반 함수에서는 yield 키워드를 사용못함.
// Generator 함수로부터 생성된 iterable은 한 번만 사용될 수 있습니다.
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

const iter = gen();

for (let n of iter) {
  // 잘 출력됩니다.
  console.log(n);
}
for (let n of iter) {
  // `iter`는 한 번 사용되었으므로, 이 코드는 실행되지 않습니다.
  console.log(n);
}

// Generator 함수 내부에서 정의된 일반 함수에서는 `yield` 키워드를 사용할 수 없습니다.
function* gen2() {
  // 아예 문법 오류가 납니다. (Unexpected token)
  function fakeGen() {
    yield 1;
    yield 2;
    yield 3;
  }
  fakeGen();
}

Generator와 Iterator

  • generator()로부터 만들어진 객체는 일반적인 iterable처럼 쓸 수 있지만 iteraoter와 관련된 특별한 성질을 갖고 있음
  • generator()로부터 만들어진 객체는 iterable protocol과 iterator protocol을 동시에 만족함.
function* gen() {
  // ...
}
const genObj = gen();
genObj[Symbol.iterator]().next === genObj.next; // true
  • 즉, Symbol.iterator를 통해 iterator를 생성하지 않고도 바로 next를 호출할수 있음.
function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

const iter = gen();

iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: false }
iter.next(); // { value: 3, done: false }
iter.next(); // { value: undefined, done: true }
  • generator() 안에서 return 키워드를 사용하면 반복이 바로 끝나면서 next()에서 반환된 객체의 속성 앞의 반환값이 저장됨.
  • 다만 return을 통해 반환된 값이 반복 절차에 포함되지는 않음.
function* gen() {
  yield 1;
  return 2; // generator 함수는 여기서 종료됩니다.
  yield 3;
}

const iter = gen();

iter.next(); // { value: 1, done: false }
iter.next(); // { value: 2, done: true }
iter.next(); // { value: undefined, done: true }

// `1`만 출력됩니다.
for (let v of gen()) {
  console.log(v);
}
  • generator()로부터 생성된 객체의 next()에 인수를 주어서 호출하면, generator()가 멈췄던 부분의 yield 표현식의 결과값은 앞에서 받은 인수가 됨.
function* gen() {
  const received = yield 1;
  console.log(received);
}

const iter = gen();
iter.next(); // { value: 1, done: false }

// 'hello'가 출력됩니다.
iter.next('hello'); // { value: undefined, done: true }
  • generator() 이런 성질은 비동기 프로그래밍을 위해 활용되기도 함.

generator 활용

//첫번째 예시
function* map(iter,mapper) {
	for(const v of iter) {
      yield mapper(v)
    }
}

function* filter(iter, test) {
  for(const v of iter) {
    if(test(v)) yield v;
  }
}

function* take(n, iter) {
  for(const v of iter) {
    if(n<=0) return;
    yield v;
    n--;
  }
}

const values = [1,2,3,4,5,6,7,8,9,10];
const result = take(3,map(filter(values, n=>n%2 ===0), n=>n*10));
console.log([...result]);
// 각 항목을 변환한 후 넘겨주기
function* map(iterable, mapper) {
  for (let item of iterable) {
    yield mapper(item);
  }
}

// 각 순서까지의 누적값을 넘겨주기
function* reduce(iterable, reducer, initial) {
  let acc = initial;
  for (let item of iterable) {
    acc = reducer(acc, item);
    yield acc;
  }
}

// 조건에 만족하는 항목만 넘겨주기
function* filter(iterable, predicate) {
  for (let item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

// 여러 iterable을 연결하기
function* concat(iterables) {
  for (let iterable of iterables) {
    yield* iterable;
  }
}

// 앞쪽 몇 개의 항목만 넘겨주기
function* take(iterable, count = Infinity) {
  const iterator = iterable[Symbol.iterator]();
  for (let i = 0; i < count; i++) {
    // `yield*`와는 다르게, iterator의 `next` 메소드를 이용하면 iterable의 일부만 가져올 수 있습니다.
    const {value, done} = iterator.next();
    if (done) break;
    yield value;
  }
}

출처: JavaScript로 만나는 세상

profile
'하루를 참고 인내하면 열흘을 벌 수 있고 사흘을 참고 견디면 30일을, 30일을 견디면 3년을 벌 수 있다.'

0개의 댓글