[Generator 함수를 이해해보자 (이론편)] 1. "Why” 제네레이터 함수를 써야 하는가?

calm·2019년 2월 28일
26
post-thumbnail

20-Resources-on-ES6-for-JavaScript-Developers.jpg

[해당 이미지는 다음 링크에서 사용했습니다 : http://welllin.net/javascript-es6-generator/]

#제네레이터함수 #코루틴 #이터레이터 #이터러블 #Async/await #왜제네레이터함수를쓸까

여기서 잠깐!

이 블로그는 제네레이터 함수 공부하며 참고한 블로그 내용을 재 가공해 작성 한 글입니다.
중복되는 용어와 해당 부분에 대해 링크를 정리했습니다.
궁금하신 분은 주소를 클릭하면 원문을 읽을 수 있습니다.

시작하는 글

비동기 관련해 블로그를 계획하고 있는데. 그 중 제네레이터 함수에 대해서 작성하려고 합니다.

Generator function은 Async/await와 함께 비동기를 동기처럼 다룰 수 있도록 기여하고 있습니다. 사실, Async/await는 제네레이터와 기반으로 비슷한 구조를 갖고 있습니다.

오늘 우리는 왜 제네레이터 함수에 관심을 가져야 하는지, 어디에 쓰이는지 마지막으로 어떻게 작동 하는지 하나씩 살펴볼 것 입니다.

구성

블로그를 이론적인 부분과 코드편으로 나눠 작성됩니다. 그 중 이론편부터 시작합니다.이론편이 내용이 많아 목차 순서대로 나눠 정리됩니다.

(각각의 모든 글 하단에 참고&인용한 블로그 링크를 추가했습니다.)

목 차

[1]. “왜” 제네레이터 함수를 써야 하는가?
[2]. “어디에” 제네레이터 함수를 써야하는가?
[3]. 제네레이터 함수의 "정의"와 "작동원리"는 무엇인가?

세부 목차

[1] “왜” 제네레이터 함수를 써야 하는가?

  1. 비동기 특성을 동기적 코드방식으로 관리 해줍니다.
  2. 이터레이터와 이터러블을 쉽게 사용 할 수 있습니다.
  3. 제네레이터 함수는 코루틴 특성을 가지고 있습니다.
  4. 제네레이터 함수는 동시적인 특성을 갖고 있습니다.
  5. 제네레이터 함수는 비동기적 특성을 갖고 있습니다.
  6. 메모리 효율에 기여할 수 있습니다.

왜 제네레이터 함수를 써야하는지 그 이유에 대해서 하나씩 정리해봅시다.

1. 비동기 특성을 동기적 코드방식으로 관리 해줍니다.

자바스크립트는 성능을 위해 비동기를 채택합니다. 콜백함수는 비동기를 관리하는 가장 쉽고 흔한 방법입니다.

하지만 콜백을 중첩하게 되면 콜백지옥이라는 코드 가독성의 문제가 발생합니다. 이때 프로미스가 등장해 함수 중첩을 함수의 연쇄적 호출표현으로 변경했습니다.

그러나 then( ),catch( )함수가 이중,삼중으로 겹치게 되면 이 또한 다른 가독성의 문제를 일으킵니다. 그래도 아직은 복잡합니다.

다시 우리를 구원 할 Async/await가 등장합니다. 이것은 비동기적 구조를 동기적으로 작성할 수 있게도와줍니다.
제네레이터 함수도 동기적으로 작성할 수 있습니다. 사실 Async/await 는 제네레이터 기반해 만들어졌습니다.

2. 이터레이터와 이터러블을 쉽게 사용할 수 있습니다.

제너레이터 함수에는 next() 코드와 [Symbol.iterator]() 코드를 이미 내장(built-in)하고 있습니다.
반대로 말하면 매번 직접 next() 메서드[Symbol.iterator]( )을 입력해야한다는 의미입니다.

다음의 코드는 "This", "is, "iterable"이라는 키워드를 반환하는 이터레이터 코드입니다

직접 이터레이터를 작성한 코드입니다.

const iterableObj = {
  [Symbol.iterator]() {  
    let step = 0;
    return {
      next() {
        step++;
        if (step === 1) {
          return { value: 'This', done: false};
        } else if (step === 2) {
          return { value: 'is', done: false};
        } else if (step === 3) {
          return { value: 'iterable.', done: false};
        }
        return { value: '', done: true };
      }
    }
  },
}
for (const val of iterableObj) {
  console.log(val);
}

객체가 이터레블 하기 위해서는 [Symbol.iterator]()메서드를 추가해야 합니다. 이를 통해 객체는 이터레이터 프로토콜을 따르게 됐습니다.

그리고 이 객체를 next()메서드를 통해 객체 값을 하나씩 하나씩 출력합니다.

step = 0이고 step이 ++ 될 때마다,
step 1일때, 2일때, 3일때,

이터레이터가 순회하면서 다음의 값을 출력합니다.

// This
// is 
// iterable.

모든 이터레이터가 출력이 완료되면 이터레이터는 더 이상 출력할 값이 없습니다.

{ value: '', done: true }

위 내용을 제네레이터로 다시 작성해봅니다.

function * iterableObj() {
  yield 'This';
  yield 'is';
  yield 'iterable.'
}
for (const val of iterableObj()) {
  console.log(val);
}

iterableObj()제네레이터 객체를 반환를 반환해 for..of문 을 통해 다음의 값을 출력합니다.

차이점은 yield 입니다.

위 이터레이터와 동일한 값을 반환합니다.

// This
// is
// iterable.

사실, 제네레이터 객체는 이터레이터 객체와 같다고 생각하면 됩니다.

자세한 내용은 다음편 "제네레이터 함수는 무엇인가"에서 코드로 설명하겠습니다.

이 제네레이터 함수는 yield(넘겨주다)를 통해 'This','is', 'iterable'을 반환합니다.

핵심은 직접 [Symbol.iterator](), next()메서드 하나씩 작성해서 사용하는 것과 미리 구현된 기능을 사용하는 것의 차이 입니다.

제네레이터 함수는 이터레이터의 특성이 이미 정리되어 포함되 있습니다.

제네레이터가 반환하는 객체는 제네레이터 객체지만 같은 이터레이터 객체가 포함되어 있습니다.
그래서 next( )메서드를 호출 할 수 있는 이유 입니다.

3. 제네레이터 함수는 코루틴 특성을 가지고 있습니다.

단지 제네레이터가 이터레이터와 같다면 굳이 왜 제네레이터 함수를 사용할까요, 그렇지 않습니다.

코루틴을 설명하기 전에 서브루틴이란 개념이 무엇인지 알아봅시다.

위키백과를 검색하면 다음과 같이 언급하고 있습니다.

“함수(function), 서브루틴(subroutine), 루틴(routine), 메서드(method), 프로시저(procedure)는 소프트웨어에서 특정 동작을 수행하는 일정 코드 부분을 의미한다.
함수는 대부분의 프로그래밍 언어에서 지원하는 기능으로, 하나의 큰 프로그램을 여러 부분으로 나누어주기 때문에 같은 함수를 여러 상황에서 여러 차례 호출할 수 있으며 일부분을 수정하기 쉽다는 장점을 가진다”

쉽게 생각해, 서브루틴(subroutine)을 함수라고 생각하면 됩니다. 도서 “러닝 자바스크립트(Learning Javascript, 한빛미디어, p273)을 보시면 함수를 값을 반환하는 서브루틴으로서 함수라고 함수의 특징을 말하고 있습니다.”

자. 그럼 코루틴이 무엇인지 알아봅시다.

코루틴(coroutine)이란
루틴의 일종으로서, 협동 루틴이라 할 수 있다(코루틴의 "Co"는 with 또는 togather를 뜻한다). 상호 연계 프로그램을 일컫는다고도 표현가능하다. 루틴과 서브 루틴은 서로 비대칭적인 관계이지만, 코루틴들은 완전히 대칭적인, 즉 서로가 서로를 호출하는 관계이다.

코루틴은 suspend/resume가 가능하며, 함수가 call/return되는 것과 비교하면 더 일반화된 형태라 할 수 있다. (함수는 suspend/resume이 빠진 코루틴인 셈이다.)
-위키백과 중-

suspend(보류/정지) :
: to defer to a later time on specified conditions
: to hold in an undetermined or undecided state awaiting further information

resume(되돌아감/재개) :
: to return to or begin again after interruption
: to take back to oneself

코루틴의 정의를 알았으니, 코루틴의 특성을 서브루틴과 비교해 봅시다.

일반함수(서브루틴)은 caller가 함수를 call하면, callee함수는 콜스택에 들어와 작동합니다. 모두 아는 것처럼 자신의 로직을 수행완료하면 값을 return 하고 콜스택에서 사라집니다.
콜스택을 나간 함수는 다시 붙잡을 수 없으며 실행할 방법도 없습니다.

만약 다시 실행된다해도, 함수가 종료된 지점이 아닌 “다시” 함수의 “처음부터” 시작합니다.
즉. 일반함수(서브루틴)는 실행되는 시작점 언제나 같습니다(코드 맨 위 입니다).

반면 코루틴(coroutine)의 특성종료된 함수라고 해도 다시 실행될 때, 그 진입점을 자신이 원하는 곳으로 커스터마이징 할 수 있습니다.
코루틴 특성을 가진 제네레이터 함수는 이유는 제네레이터 객체가 함수의 스택 프레임(각종 컨텍스트)을 저장하고 있기 때문입니다.

객체가 yield 표현식을 만나면 함수가 잠시 suspend되는 동시에 함수 컨텍스트를 복사하고 콜스택을 벗어납니다. 그러다 caller가 next()메서드를 call하면 저장해 둔 스택 프레임들이 복원됩니다.
잠시 suspend된 함수는 복원된 컨텍스트에서 중단된 지점부터 resume 실행을 이어 갑니다.

그래서 코루틴은 “정지-실행-정지-실행” 의 루프를 진행할 수 있습니다.
코루틴의 특성은 Generator 이외에도 Async/await 코루틴도 있습니다,

4. 제네레이터 함수는 동시적인 특성을 갖고 있습니다.

“실행-정지-실행-정지”의 코루틴 형태를 잘 이용하면, 협력형 멀티태스킹 방식으로, 쓰레드 프로그래밍 없이 동시성 프로그래밍이 가능해집니다.
쓰레드는 필요한 비용에 비해 신경써야 할 것들이 너무 많은 단점이 있지만, 코루틴은 OS의 암묵적인 스케쥴링이나 컨텍스트 스위칭 오버헤드, 세마포어 설정 같은 고민으로 부터 자유롭습니다.

5. 제네레이터 함수는 비동기적 특성을 갖고 있습니다.

동기적으로 작동되는 코루틴 특성이 비동기 코드를 작성하는데 도움을 줄 수 있습니다.이는 코드가 작성 된 형태는 동기적이나 실행되는 방식은 비동기라는 의미입니다.
비동기 프로그래밍의 대표적인 방식인 콜백함수는 단계를 거듭할 수록 코드의 가독성이 떨어지는 콜백지옥을 경험하게 됩니다. 예를 들어 Async/await 경우 비동기적 특성을 동기적인 형태로 작성합니다.

6. 메모리 효율에 기여할 수 있습니다.

제네레이터 함수는 느긋한 계산(Lazy-Evaluation)을 통해 필요 할 때 값을 요구하기 사용해서 메모리를 효율적으로 사용할 수 있습니다.
느긋한 계산이란 값을 받기 까지 그 시간을 지연시키고 늦추는 것을 말합니다.
즉 값이 필요하지 않을 때는 조용히? 있다가 필요할 때가 되면 값을 요구하는 겁니다.
yield를 값을 넘겨주거나 저장하고, 값이 필요할 때 next( )메서드 호출해 느긋하게 시간을 지연하며 작업 할수 있습니다.

마무리

이것으로 “왜” 제네레이터 함수를 써야 하는가? 에 대해 그 이유를 정리해봤습니다.

글 서두에 언급한대로 제네레이터 역시 이터레이터와 같은 것이기 때문에, 이터레이터에 대한 사전에 공부가 필요합니다.

이를 위해 이터러블과 이터레이터 정의에 대해 정리합니다.
제네레이터 자체라도 분량이 워낙 많아 이터레이터 따로 글을 작성하려고 합니다.

iterable 
'순회가능한' 객체란 Symbol.iterator 심볼을 속성으로 가지고 있고, 이터레이터 객체를 반환하는 객체를 뜻합니다.
 이런 스펙을 이터러블 프로토콜 이라고 하고 이 프로토콜을 지킨 객체를 이터러블 객체라고 합니다.
iterator
이터러블 객체가 [Symbol.iterator]() 메소드로 반환하는 이터레이터 객체는 무엇일까요?
 next() 메소드를 구현하고 있고, done과 value 속성을 가진 객체를 반환하는 객체입니다.. 이런 스펙을 이터레이터 프로토콜이라고 합니다.

다음 편에서는 “어디에” 제네레이터 함수를 써야하는가?, 제네레이터 함수란 “무엇”인가? “ 에 대해 설명하겠습니다.

참고&인용 레퍼런스 링크

이터레이터 / 제네레이터 코드 : https://codeburst.io/understanding-generators-in-es6-javascript-with-examples-6728834016d5

코루틴 부분 : https://medium.com/@jooyunghan/코루틴-소개-504cecc89407
동시성, 비동기성 부분 : https://gist.github.com/qodot/ecf8d90ce291196817f8cf6117036997
제네레이터 함수 정의 : https://wonism.github.io/javascript-generator/
이터러블, 이터레이터 개념부분 : https://gist.github.com/qodot/ecf8d90ce291196817f8cf6117036997
영어단어 해석 : https://www.merriam-webster.com/dictionary/suspend

profile
공부한 내용을 기록합니다

1개의 댓글

comment-user-thumbnail
2020년 10월 11일

궁금했던 내용이었는데 친절하게 설명이 되어있어 많은 도움이 되었습니다! 좋은 글 감사합니다 (_ _)

답글 달기