06-1 반복기 이해하기

반복기와 반복기 제공자

1. next 라는 이름의 메서드를 제공한다.

2. next 메서드는 value, done 이라는 두 개의 속성을 가진 객체를 반환한다.

 

createRangeIterable 함수는 next 메서드를 return 해서 반복기 역할을 제공하고, 이를 '반복기 제공자(iterable)' 라고 한다.

사용자가 타입스크립트로 for...of 구문을 작성하면 TSC 컴파일러는 반복기 제공자와 반복기를 사용하는 코드로 바꿔준다.

export const createRangeIterable = (from: number, to: number) => {
  let currentValue = from

  return {
    next() {
      const value = currentValue < to ? currentValue++ : undefined
      const done = value == undefined

      return {value, done}
    }
  }
}

const iterator = createRangeIterable(1, 4) // 1부터 4까지 반복기, 함수 호출일 뿐 반복기는 동작하지 않음

while(true) {
  const {value, done} = iterator.next() // next 메서드를 호출하면서 반복기가 동작한다.
  if(done) break

  console.log(value) // 1 2 3
}

 

반복기는 왜 필요한가?

반복기 제공자는 어떤 범위의 값을 한 번에 생성해서 배열에 담지 않고 값이 필요할 때만 생성한다.

따라서 시스템 메모리의 효율성이라는 관점에서 보았을 때 메모리를 훨씬 적게 소모한다.

 

for...of 구문과 [Symbol.iterator] 메서드

위의 코드에서 createRangeIterable 함수를 for...of 구문으로 작성하면 아래 오류가 발생하고, 이 오류를 해결하기 위해서는 클래스로 구현해야 한다.

'Type '{ next(): { value: number; done: boolean; }; }' is not an array type or a string type or does not have a '[Symbol.iterator]()' method that returns an iterator.'

 

클래스 메서드느 자바스크립트의 function 키워드가 생략되었을 뿐 사실상 function 키워드로 만들어진 함수이다.

function 키워드로 만들어진 함수는 내부에서 this 키워드를 사용할 수 있고, next 함수 또한 function 키워드가 생략된 메서드이므로 컴파일러가 next의 this로 해석하지 않게 하는 자바스크립트의 유명한 코드 트릭이다.

export class RangeIterable {
  constructor(public from: number, public to: number) {}

  [Symbol.iterator]() {
    const that = this
    let currentValue = that.from

    return {
      next() {
        const value = currentValue < that.to ? currentValue++ : undefined
        const done = value == undefined

        return {value, done}
      }
    }
  }
}

const iterator = new RangeIterable(1, 4)

for(let value of iterator) {
  console.log(value) // 1 2 3
}

 

Iterable<T>와 Iterator<T> 인터페이스

타입스크립트는 반복기 제공자에 Iterable<T>와 Iterator<T> 제네릭 인터페이스를 사용할 수 있다.

 


06-2 생성기 이해하기

생성기는 function* 키워드로 만든 함수를 의미한다.

yield는 반드시 function* 키워드를 사용한 함수에서만 호출할 수 있고, yield는 return 키워드처럼 값을 반환한다.

export function* generator() {
  console.log('generator started.')

  let value = 1

  while(value < 4)
    yield value++

  console.log('generator fininsed.')
}

for(let value of generator())
  console.log(value)
  
// generator started.
// 1
// 2
// 3
// generator fininsed.

 

setInterval 함수와 생성기의 유사성

생성기가 동작하는 방식을 '세미코루틴' 이라고 한다. 

세미코루틴은 타입스크립트처럼 단일 스레드로 동작하는 프로그래밍 언어가 다중 스레드로 동작하는 것처럼 보이게 한다.

프로그램의 출력 내용만 보면 생성기 방식과 동일하지만, setInterval 함수가 동작하는 구조는 멀티 스레드가 동작하는 방식과 유사하다.

따라서 생성기는 일반적인 타입스크립트 코드가 다른 방식으로 동작하는 것을 기억해야 한다.

 

단일 스레드(싱글 스레드) vs 다중 스레드(멀티 스레드)

스레드란, 프로세스가 할당받은 자원을 시용하는 실행의 단위이다.

단일 스레드는 메인 스레드 하나만 가지고 작업을 처리하고, 작업을 순서대로 처리한다. (하나의 레지스터와 스택)

다중 스레드는 메인 스레드 외에 추가적인 스레드를 이용하여 병렬적으로 작업을 처리한다. context switching이 빠르게 일어나는 것이기 때문에 사용자 입장에서 프로그램들이 동시에 수행되는 것처럼 보인다.

 

let count = 0

console.log('program started.')

const id = setInterval(() => {
  if(count >= 3) {
    clearInterval(id)
    console.log('program finished.')
  } else {
    console.log(count++)
  }
}, 1000)

// program started.
// 0
// 1
// 2
// program finished.

 

function* 키워드

  1. function* 키워드로 함수를 선언한다.
  2. 함수 몸통 안에 yield문이 있다.

 

yield 키워드

  1. 반복기를 자동으로 만들어 준다.
  2. 반복기 제공자 역할도 수행한다.

 

function* 키워드로 선언된 함수만 생성기이고, 화살표 함수로는 생성기를 만들 수 없다.

function* getPage(pageSize: number = 1, list: number[]) {
  let output = []
  let index = 0

  while (index < list.length) {
    output = []

    for (let i = index; i < index + pageSize; i++) {
      if (list[i]) {
        output.push(list[i])
      }
    }

    yield output
    index += pageSize
  }
}

let page = getPage(3, [1, 2, 3, 4, 5, 6, 7, 8])

console.log(page.next()) // { value: [ 1, 2, 3 ], done: false }            
console.log(page.next()) // { value: [ 4, 5, 6 ], done: false }
console.log(page.next()) // { value: [ 7, 8 ], done: false }
console.log(page.next()) // { value: undefined, done: true }

 

 

yield* 키워드

yield는 단순히 값을 대상으로 동작하지만, yield*는 다른 생성기나 배열을 대상으로 동작한다.

function* gen12() {
  yield 1
  yield 2
}

function* gen12345() {
  yield* gen12()
  yield* [3, 4]
  yield 5
}

for(let value of gen12345()){
  console.log(value) // 1 2 3 4 5
}

 

yield 반환값

yield 연산자의 반환값은 반복기의 next 메서드 호출 때 매개변수에 전달하는 값이다.

function* gen(){
  let count = 3
  let select = 0

  while(count--){
    select = yield `select ${select}`
  }
}

const random = (max, min = 0) => Math.trunc(Math.random() * (max - min))

const iter = gen()

while(true){
  const {value, done} = iter.next(random(10, 1))

  if(done) {
    break
  } else {
    console.log(value)
  }
}

// select 0
// select 2
// select 5

 

 

 

참고자료

Do it! 타입스크립트 프로그래밍

+ Recent posts