generator로 lazy loading하기


🔗 [iterator & generator](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Iterators_and_Generators)
## 자바스크립트에서의 iterator(반복자) 타입이란

자바스크립트에서 iterator 타입은 next() 메서드를 구현하고, ``next()`` 메서드는 ``{value: "", done: ""}`` 객체를 리턴한다. 

🔗 [MDN iterator protocal 정의](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol)

**참고** 소스가 본 확장툴에서는 정상동작하지 않습니다. 브라우저 콘솔에서 테스트하시기 바랍니다. 
```js
const iter = {
    next: function() {
        return {value: undefined , done: false};
    }
};
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
    var nextIndex = start;
    var n = 0;

    var rangeIterator = {
       next: function() {
           var result;
           if (nextIndex < end) {
               result = { value: nextIndex, done: false }
           } else if (nextIndex == end) {
               result = { value: n, done: true }
           } else {
               result = { done: true };
           }
           nextIndex += step;
           n++;
           return result;
       }
    };
    return rangeIterator;
}
// makeRangeIterator(1, 2, 5);
let iter = makeRangeIterator(1, 18, 3);

let result = iter.next();
while (!result.done) {
   console.log(result.value);
   result = iter.next();
}
```
iterator(반복자)를 잘 사용하면 유용하지만, 내부적으로 상태를 유지해야하므로 주의해서 사용해야한다는 단점이 있다. 
## generator함수의 function* 문법 

generator 함수는 'function*' 문법을 사용하여 작성된다. 최초 호출시 어떤 코드도 실행되지 않고 iterator타입을 반환한다. 
```js
function* abcGen() {
    yield 'a';
    yield 'b';
    yield 'c';
}

const abc = abcGen();

console.log(abc.next());
console.log(abc.next());
console.log(abc.next());
console.log(abc.next());

앞서 작성했던 iterator를 generator로 구현해보면 아래와 같습니다. 
function* makeRangeGenerator(start = 0, end = Infinity, step = 1) {
    let n = 0;
    for (let i = start; i < end; i += step) {
        n++;
        yield i;
    }
    return n;
}

const rangeGenerator = makeRangeGenerator(1, 10, 3);

console.log(rangeGenerator.next());
console.log(rangeGenerator.next());
console.log(rangeGenerator.next());
console.log(rangeGenerator.next());
next() 메서드에 파라미터로 값을 전달할 수 도 있습니다. 
function* makeRangeGenerator3(start , end, step) {
    let n = 0;
    for (let i = start; i < end; i += step) {
        n++;
        let isBreak = yield i;
        // console.log(isBreak);
        if (isBreak === true) {
            break;
        }
    }

    return n;
}

const rangeGenerator3 = makeRangeGenerator3(1, 20, 2);

console.log(rangeGenerator3.next());
console.log(rangeGenerator3.next());
console.log(rangeGenerator3.next());
console.log(rangeGenerator3.next());
console.log(rangeGenerator3.next(false));
console.log(rangeGenerator3.next());
console.log(rangeGenerator3.next());
console.log(rangeGenerator3.next());
console.log(rangeGenerator3.next());
return() 메서드를 사용하면 파라미터로 value 를 전달할 수 있습니다. 
const rangeGenerator4 = makeRangeGenerator3(1, 10, 2);

console.log(rangeGenerator4.next());
console.log(rangeGenerator4.next());
console.log(rangeGenerator4.next());
console.log(rangeGenerator4.next());
console.log(rangeGenerator4.next());
console.log(rangeGenerator4.next());
console.log(rangeGenerator4.next());
console.log(rangeGenerator4.return("종료"));
console.log(rangeGenerator4.next());
```
## generator의 활용

- custom iterables
- lazy/infinite sequences
- state machines
- data processing
- data streams
## 활용예제1 - UUID 생성기
```js
function* UUIDGenerator() {
    let d, r;
    while(true) {
        yield 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            r = (new Date().getTime() + Math.random()*16)%16 | 0;
            d = Math.floor(d/16);
            return (c=='x' ? r : (r&0x3|0x8)).toString(16);
        });
    }
};
const uuid = UUIDGenerator();

for (let i = 0; i < 100; i++) {
    let result = uuid.next().value;
    console.log(result);
}

console.log(uuid.next());
console.log(uuid.next());
console.log(uuid.next());
```
## 활용예제 2 - custom iterables
```js
const cardDeck = ({
    suits: ["♣️", "♦️", "♥️", "♠️"],
    court: ["J", "Q", "K", "A"],
    [Symbol.iterator]: function* () {
      for (let suit of this.suits) {
        for (let i = 2; i <= 10; i++) yield suit + i;
        for (let c of this.court) yield suit + c;
      }
    }
  })

[...cardDeck];
```
## 활용예제 3 - 무한 스크롤 페이지 가져오기
```js
getSwapiPagerator = (endpoint) =>
  async function* () {
    let nextUrl = `https://swapi.dev/api/${endpoint}`;
    while (nextUrl) {
      const response = await fetch(nextUrl);
      const data = await response.json();
      nextUrl = data.next;
      yield* data.results;
    }
  }
// Example adapted from Luciano Mammino's https://www.nodejsdesignpatterns.com/blog/javascript-async-iterators/
starWars = ({
    characters: {
      [Symbol.asyncIterator]: getSwapiPagerator("people")
    },
    planets: {
      [Symbol.asyncIterator]: getSwapiPagerator("planets")
    },
    ships: {
      [Symbol.asyncIterator]: getSwapiPagerator("starships")
    }
  })
{
    const results = [];
    for await (const page of starWars.ships) {
      console.log(page.name);
      results.push(page.name);
      yield results;
    }
  }
```

댓글

이 블로그의 인기 게시물

Session 대신 JWT를 사용하는 이유

VSCode에서의 VIM 단축키와 키보드 구매 가이드

우분투에서 테스트링크(testlink)와 맨티스(mantis)로 테스팅 서버 구성하기