📝

제너레이터

1. 제너레이터(Generator)란

제너레이터란 ES6에서 새롭게 도입된 개념의 함수로, 코드 실행을 중간에 멈췄다가 필요한 시점에 재개할 수 있는 특징을 가진다. 일반적인 함수는 함수를 호출해 실행되는 순간 제어할 수 없지만 제너레이터는 이것이 가능하고 이를 이용해 비동기 처리를 동기 처리처럼 프로그래밍 할 수 있다.

1.1 제너레이터 함수 정의

일반 함수를 정의하는 방식과 같지만, 다른 몇 가지가 있다. 첫 번째, function 키워드 옆에 별표(*, 에스터리스크)를 붙여서 사용한다. 두 번째, 함수 내에서 return과 비슷한 개념인 yield를 사용해 코드의 실행을 제어 가능하게 한다. 그리고 화살표 함수, new 연산자는 사용 불가하다.
function* 제너레이터1() { yield 1; yield 2; yield 3; } // 기본형 function* 제너레이터2() { for (const element of [1, 2, 3]) { yield element; } } function* 제너레이터3() { yield* [1, 2, 3]; } // yield*으로 또 다른 이터러블 객체를 위임해서 사용할 수 있다. const 이터레이터 = 제너레이터1(); console.log(이터레이터.next()) console.log(이터레이터.next()) console.log(이터레이터.next()) console.log(이터레이터.next()) console.log(이터레이터.next())
일반적으로 제너레이터1 함수처럼 사용하지만 여러 개의 yield가 있는 경우 제너레이터2, 3 함수처럼 표현도 가능하다.
여기서 yield를 사용하면 비동기처리를 동기처럼 프로그래밍 할 수 있다.
function* 제너레이터1() { yield 비동기함수1(); yield 비동기함수2(); yield 비동기함수3(); }
이는 async/await 방식으로도 처리할 수 있다. 따라서 각각의 특성을 이해하고, 용도에 맞게 사용하는 편이 좋다.
async function 제너레이터1() { await 비동기함수1(); await 비동기함수2(); await 비동기함수3(); }
관련되어 다양한 아티클을 확인하고 싶다면 비동기처리 async await generator로 google에 검색해보길 권한다.

1.2 제너레이터의 특징

제너레이터 함수는 일반적인 자바스크립트 함수와는 다른 특징을 가진다. 함수 동작을 빠져나갔다가 다시 돌아올 수 있다는 점, 실행 시 Generator 객체를 생성해 반환한다는 점, 반복되는 작업을 무한 루프와 yield로 구현할(비동기 통신도 무한 재실행 구현할 수 있다) 수 있다는 점이 대표적이다.

1.2.1 함수 동작을 빠져나갔다가 다시 돌아올 수 있다

일반적인 함수는 함수를 한 번 실행시키면, 함수 내부에서 return을 만날 때까지 함수를 구성하는 코드들을 위에서부터 아래로 계속 실행시킨다. 하지만 제너레이터 함수는 yield 명령어를 통해 동작 중에 함수를 빠져나올 수 있을 뿐 아니라 함수 외부에서 제너레이터를 다시 호출함으로써 코드를 중단점에서부터 다시 실행시킬 수 있다. 즉, 함수의 제어권을 함수 호출자와 주고 받는다고 볼 수 있다.

1.2.2 Generator 객체를 생성해 반환한다

제너레이터 함수를 호출했을 때, 함수 내부의 코드를 즉시 실행하는 것이 아니라 우선 Generator 객체를 생성(이터러블 생성)해 반환한다.
콘솔에서 아래의 코드를 실행해보면 제너레이터 함수 실행 시 반환되는 객체를 확인할 수 있다.
// 제너레이터 함수 generatorFunc를 생성 function* generatorFunc() {}; // generatorFunc의 반환값을 console.dir로 출력 console.dir(generatorFunc());
제너레이터 함수 generatorFunc 내부에서는 수동으로 return을 지정해주지 않았지만, 실행시키면 아래처럼 generatorFunc 객체가 반환되어 console.dir로 출력된 구조를 확인할 수 있다.
notion imagenotion image
[[Prototype]]이라는 속성을 가지고 있다는 점에 주목하자. 이 객체는 Generator라는 프로토타입 객체에서부터 복사된 객체, 즉 인스턴스(Instance)이다. 따라서 제너레이터 함수는 Generator라는 객체의 인스턴스를 생성하는 생성자 역할을 하는 동시에 함수를 실행하는 역할을 수행한다고 볼 수 있다.
💡
[[Prototype]] 프로퍼티를 통해 반환된 객체가 인스턴스임을 증명할 수 있는 이유가 뭘까?
자바스크립트는 프로토타입 기반의 언어이다. 원형이 있는 객체를 복사하면, 새로 복사된 객체는 원형이 된 객체를 참조하는 [[Prototype]] 속성을 가지게 된다.
제너레이터 함수를 실행하여 반환된 객체의 프로퍼티를 확인하면, [[Prototype]] 속성이 Generator라는 프로토타입 객체를 참조하고 있음을 알 수 있다. 그러므로 제너레이터 함수 실행 시 반환되는 객체는 Generator 객체를 원형으로 복제된 인스턴스라는 것을 추론 가능하다.
생성된 이터러블은 아래와 같은 코드로는 확인할 수 없다. 일반적인 이터레이터 객체라면 해당 메서드로 순회가 가능한지(enumerable) 판단할 수 있다.
Object.getOwnPropertyDescriptors(이터레이터)

1.3 제너레이터 객체

1.3.1 제너레이터 객체 메서드

앞서 제너레이터 특징을 통해 제너레이터 함수 호출시 제너레이터 객체가 반환된다는 점을 알았다. 반환된 제너레이터 객체는 이터러블이면서 이터레이터인데, 때문에 제너레이터 객체도 이터레이터 메서드인 next 메서드를 사용할 수 있다. 더불어 제너레이터 객체는 이터레이터에는 없는 return, throw 메서드를 갖는다. 제너레이터 객체 메서드를 호출하면 iteratorResult 객체를 반환하며 각각의 메서드는 다음과 같이 동작한다.
notion imagenotion image
  • next
    • next 메서드를 호출하면 가장 가까운 yield 표현식까지 코드 블록을 실행하고, 산출하고자 하는 값을 iteratorResult의 value값으로 반환한다. 즉, next 메서드는 value 프로퍼티 값으로 yield된 값을, 그리고 done 프로퍼티 값으로 false를 갖는 iteratorResult 객체를 반환한다. 함수 코드 실행이 끝난 뒤 next 메서드를 호출한 경우, yield된 값이 없고 코드 실행이 끝났기 때문에 undefined와 true를 반환한다.
  • return
    • 산출값을 반환하고 제너레이터를 종료하는 메서드이다. 인수로 전달받은 값을 value 프로퍼티 값으로 가지며, 함수 코드 실행을 끝내므로 done 프로퍼티 값을 true로 갖는 iteratorResult 객체를 반환한다.
  • throw
    • 제너레이터 안에 에러를 던지는 메서드로 인수로 전달받은 에러를 발생시킨다. value 프로퍼티로 undefined를, 그리고 done 프로퍼티 값으로 true를 갖는 iteratorResult 객체를 반환한다.
       

1.3.2 제너레이터 객체 메서드 사용 예제

다음 제너레이터 함수를 활용하여 제너레이터 객체 메서드를 사용해보자.
function* 순서() { try { yield "1번"; yield "2번"; yield "끝!"; } catch (e) { console.error(e); } } const 제너레이터 = 순서();
 
① next 메서드 사용하기
① console.log(제너레이터.next()); // {value: '1번', done: false} console.log(제너레이터.next()); // {value: '2번', done: false} console.log(제너레이터.next()); // {value: '끝!', done: false} console.log(제너레이터.next()); // {value: undefined, done: true}
제너레이터 함수 안에 세개의 yield 표현식이 있기 때문에 세번째 next 메서드까지는 각각 yield 표현식에서 yield된 값을 value 프로퍼티로 할당하고, 함수가 끝까지 실행되지 않았기 때문에 done 프로퍼티 값으로 false를 할당한다. 하지만 네번째 next 메서드는 코드 실행이 끝난뒤 호출했기 때문에 {value: undefined, done: true} 를 반환한다.
for … of문으로도 사용할 수 있다. 다만 생성된 이터러블은 단 한번만 순회가 가능하다.
 
② return 메서드 사용하기
console.log(제너레이터.next()); // {value: '1번', done: false} ② console.log(제너레이터.return('종료합니다~')); // {value: '종료합니다~', done: true} console.log(제너레이터.next()); // {value: undefined, done: true}
첫번째 next 호출은 첫번째 yield 표현식까지 실행한다. 제너레이터 함수가 끝까지 실행되지 않았으므로 done 프로퍼티에는 false가 할당된다. return 메서드를 호출하여 제너레이터를 종료한다. 이 때 value값은 인수로 전달받은 ‘종료합니다~’이다. 함수 코드 실행이 끝났으므로 done프로퍼티에 true가 할당된다. return으로 제너레이터를 종료했으므로 next 메서드를 호출하면 {value: undefined, done: true}를 반환한다.
 
③ throw 메서드 사용하기
console.log(제너레이터.next()); // {value: '1번', done: false} ③ console.log(제너레이터.throw('에러던져요~')); ⓧ 에러던져요~ // {value: undefined, done: true} console.log(제너레이터.next()); // {value: undefined, done: true}
첫번째 next 호출은 첫번째 yield 표현식까지 실행한다. throw 메서드를 호출하여 인수로 전달받은 ‘에러던져요~’를 발생시킨다. 에러를 던져 제너레이터가 종료되었으므로 {value: undefined, done: true}를 반환한다. 제너레이터가 종료되었기 때문에 next메서드 호출시 {value: undefined, done: true}를 반환한다.
 

2. 이터레이터와 제너레이터 비교

2.1 이터레이터와 이터러블

2.1.1. interable

이터러블이란 for문과 같은 반복 구문을 적용할 수 있는 리스트 처럼 반복 가능한 객체를 말한다. 우리가 알고 있는 배열, 문자열, Map, 그리고 Set도 모두 이터러블이다. 우리가 배우는 제너레이터 객체는 이터러블이기 때문에 for... of 문을 사용할 수 있고, 스프레드 문법도 사용할 수 있다. 배열의 prototype를 보면 바로 뒤에서 배울 interator를 반환하는 Symbol.iterator 메서드를 가지고 있다. 즉 이터러블은 이터레이터를 리턴하는 [Symbol.iterator]() 메서드를 가진 객체를 말한다.
notion imagenotion image

2.1.2. iterator

이터레이터는 위에서 언급한 것과 같이 이터러블에 Symbol.iterator 메소드를 호출했을 때 반환되는 값을 말한다. iteratorResult 객체를 리턴하는 next() 라는 메소드를 가지고 있는데, 이를 사용하여 이터러블을 순회할 수 있다. next() 메서드는 value와 done 속성을 가진 객체를 반환한다. 여기서 value 값으로는 모든 타입의 값을 가질 수 있고, 최근 순회 요소를 뜻한다. done은 순회 완료 여부를 뜻하는 boolean 타입을 가진다. iterator가 끝에 도달하지 않은 경우 value는 이용이 가능하고 done은 false이다. 더 이상 값이 없으면 value의 undefined를 반환하고, done은 true가 된다.
const array = [1, 2, 3]; let iter = array[Symbol.iterator](); console.log(iter.next()); // {value: 1, done: false} console.log(iter.next()); // {value: 2, done: false} console.log(iter.next()); // {value: 3, done: false} console.log(iter.next()); // {value: undefined, done: true}

2.2 이터레이터와 제너레이터

예를 들어 1부터 6까지의 요소를 가진 배열 arr을 이터레이터와 제너레이터로 만들어 비교해보자. *1)
const arr = [1, 2, 3, 4, 5, 6];
단순히 배열 리터럴 표현을 통해 위와 같이 배열을 간단히 만들 수 있지만, 만약 배열 안에 들어있는 요소가 엄청 많다면, 안에 들어있는 모든 요소가 메모리 공간을 차지하기 때문에 메모리 효율이 낮아진다. 이런 경우 이터레이터를 사용한다면 메모리 효율을 높일 수 있다.
function getIterator() { let num = 1; const next = () => num > 6 ? { done: true } : { done: false, value: num++ }; return { [Symbol.iterator]: () => ({ next }), next, }; } console.log([...getIterator()]); // [1, 2, 3, 4, 5, 6]
이터레이터 객체를 만들기 위해선 앞에서 살펴본 것처럼 이터러블이 가진 Symbol.iterator 메서드를 사용하여 이터레이터를 만들 수 있다. 그러나 우리와 같은 초보들에게 있어 사용이 다소 어렵게 느껴질 수 있다. 이러한 어려움을 도와줄 수 있는 것이 바로 제너레이터이다.
function* getGenerator() { for (let num = 1; num <= 6; num++) { yield num; } } console.log([...getGenerator()]); // [1, 2, 3, 4, 5, 6]
위에서 보는 것처럼 제너레이터는 단지 제너레이터 함수를 선언하는 것만으로도 이터레이터 객체를 훨씬 쉽고 간결하게 만들 수 있다. 제너레이터 함수가 반환한 제너레이터 객체는 이터러블이면서 동시에 이터레이터이다.
제너레이터는 yield 키워드를 통해 함수 실행을 중단할 수 있으며, next 메서드로 yield 줄의 결과를 이터레이터 객체로서 반환한다.
function* 아무말제너레이터() { yield "오늘 점심 뭐 먹지"; yield "자바스크립트 너무 어렵다"; yield "청소하기 귀찮아"; } const 아무말 = 아무말제너레이터(); console.log(아무말.next()); // {value: "오늘 점심 뭐 먹지", done: false} console.log(아무말.next()); // {value: "자바스크립트 너무 어렵다", done: false} console.log(아무말.next()); // {value: "청소하기 귀찮아", done: false} console.log(아무말.next()); // {value: undefined, done: true}
함수가 종료될 때는 value는 undefined, done은 true로 나타난다.
앞에서 언급한 것처럼 아래와 같이 for … of문으로도 사용할 수 있다. 다만 생성된 이터러블은 단 한번만 순회가 가능하다.
function* 아무말제너레이터() { yield "오늘 점심 뭐 먹지"; yield "자바스크립트 너무 어렵다"; yield "청소하기 귀찮아"; } const 아무말 = 아무말제너레이터(); for (말 of 아무말){ console.log(말) }
만약 yield 키워드에 에스터리스크(*)를 붙이면, 함수 실행을 다른 이터레이터(제너레이터) 객체에 위임할 수 있다.
function* 딴짓하기() { yield "휴대폰 보기"; yield "책상 정리하기"; yield "인터넷 서핑하기"; } function* 프론트엔드공부하기() { yield "HTML, CSS 공부하기"; yield "자바스크립트 공부하기"; yield* 딴짓하기(); yield "웹사이트 구현 실습"; } const 데일리공부 = 프론트엔드공부하기(); console.log(데일리공부.next()) // {value: 'HTML, CSS 공부하기', done: false} console.log(데일리공부.next()) // {value: '자바스크립트 공부하기', done: false} console.log(데일리공부.next()) // {value: '휴대폰 보기', done: false} console.log(데일리공부.next()) // {value:'책상 정리하기', done: false} console.log(데일리공부.next()) // {value: '인터넷 서핑하기', done: false} console.log(데일리공부.next()) // {value: '웹사이트 구현 실습', done: false} console.log(데일리공부.next()) // {value: undefined, done: true}
아래와 같이 활용이 가능하다. 이러한 로직(반복되며 순차적 진행)이 필요한 곳에서 비동기 활용도 할 수 있다.
const infi = function* (c) { let x = c while (true) { yield x; x += 1 } }; const zip1 = (a, b) => Object.keys(a).map(v=>[v, b.next().value]); const zip2 = (a, b) => Object.values(a).map(v=>[v, b.next().value]); let value1 = zip1({'one':10, 'two':20}, infi(0)) let value2 = zip2({'one':10, 'two':20}, infi(1))

3. 제너레이터의 비동기 활용

3.1 회사 결재 시스템 만들기

회사에서 과장 → 부장 → 대표의 순서로 서류 결재를 받아야 한다고 가정해보고, 이런 결재 시스템의 예제를 보며 제너레이터를 이용한 비동기처리를 알아보자.
function 과장님사인받기(서류) { const 과장님사인받은서류 = `과장님 사인 받은 ${서류}`; return 과장님사인받은서류; } function 부장님사인받기(서류) { const 부장님사인받은서류 = `부장님 사인 받은 ${서류}`; return 부장님사인받은서류; } function 대표님사인받기(서류) { const 대표님사인받은서류 = `대표님 사인 받은 ${서류}`; return 대표님사인받은서류; } function 이모티콘발매(서류) { const 결과 = "개발자 이모티콘 발매"; return 결과; } function 회사결재(서류) { if (!서류) { console.log("서류가 없습니다. 확인해주세요."); return; } const 과장님사인받은서류 = 과장님사인받기(서류); if (!과장님사인받은서류) { console.log("과장한테 다녀와라"); return; } const 부장님사인받은서류 = 부장님사인받기(과장님사인받은서류); if (!부장님사인받은서류) { console.log("부장한테 다녀와라"); return; } const 대표님사인받은서류 = 대표님사인받기(부장님사인받은서류); if (!대표님사인받은서류) { console.log("이모티콘 발매는 아직 안돼~~!"); return; } const 이모티콘발매결과 = 이모티콘발매(대표님사인받은서류); return 이모티콘발매결과; } 회사결재("서류");
예제 3-1-1
예제 3-1-1번을 보면 각각 사인을 받은 서류를 실행해주는 함수들이 있고, 그 밑에 회사결재 함수가 있다. 이 예제들은 순서대로 실행이 되는 함수들이다.
function 과장님사인받기(서류) { const 과장님사인받은서류 = `과장님 사인 받은 ${서류}`; return 과장님사인받은서류; } function 부장님사인받기(서류) { const 부장님사인받은서류 = `부장님 사인 받은 ${서류}`; setTimeout(() => { return 부장님사인받은서류; }, 2000); } function 대표님사인받기(서류) { const 대표님사인받은서류 = `대표님 사인 받은 ${서류}`; return 대표님사인받은서류; } function 이모티콘발매(서류) { const 결과 = "개발자 이모티콘 발매"; return 결과; } function 회사결재(서류) { if (!서류) { console.log("서류가 없습니다. 확인해주세요."); return; } const 과장님사인받은서류 = 과장님사인받기(서류); if (!과장님사인받은서류) { console.log("과장한테 다녀와라"); return; } const 부장님사인받은서류 = 부장님사인받기(과장님사인받은서류); if (!부장님사인받은서류) { console.log("부장한테 다녀와라"); return; } const 대표님사인받은서류 = 대표님사인받기(부장님사인받은서류); if (!대표님사인받은서류) { console.log("이모티콘 발매는 아직 안돼~~!"); return; } const 이모티콘발매결과 = 이모티콘발매(대표님사인받은서류); return 이모티콘발매결과; } 회사결재("서류");
예제 3-1-2
예제 3-1-1은 마치 동기적으로 처리가 되는 것 처럼 보이지만 3-1-2 예제는 부장님이 잠시 자리를 비웠다고 가정하는 상황이다. setTimeout을 통해 부장님사인받기() 함수를 비동기처리 하고 있다. 이 때 부장님사인받기() 함수는 부장님사인받은서류를 가져오기 전에 먼저 실행이 되기 때문에 undefined 상태이다. 따라서 회사결재()를 실행하면 부장님사인받은서류에 값이 없는 상태이기 때문에 실행 결과는 `부장한테 다녀와라` 라는 결과가 나온다.
비동기함수는 순서에 상관없이 처리가 되기 때문에 이런 부분을 해결하기 위해 콜백함수를 통해 해결할 수 있지만, 콜백함수를 계속 중첩해서 사용하다보면 아래 예제와 같이 콜백지옥이 생기게 된다.
function 과장님사인받기(서류, callback) { const 과장님사인받은서류 = `과장님 사인 받은 ${서류}`; callback(과장님사인받은서류) } function 부장님사인받기(과장님사인받은서류, callback) { const 부장님사인받은서류 = `부장님 사인 받은 ${과장님사인받은서류}`; setTimeout(() => { callback(부장님사인받은서류); }, 2000); } function 대표님사인받기(부장님사인받은서류, callback) { const 대표님사인받은서류 = `대표님 사인 받은 ${부장님사인받은서류}`; callback(대표님사인받은서류); } function 이모티콘발매(서류) { const 결과 = "개발자 이모티콘 발매"; console.log(결과); } function 회사결재(서류, callback) { callback(서류, function (과장님사인받은서류) { 부장님사인받기(과장님사인받은서류, function (부장님사인받은서류) { 대표님사인받기(부장님사인받은서류, function (대표님사인받은서류) { 이모티콘발매(대표님사인받은서류); }); }); }); } 회사결재('서류', 과장님사인받기)
예제 3-1-3
3-1-3 예제와 같은 콜백 지옥을 해결하기 위해 Promise가 등장했다. 3-1-3 예제의 콜백지옥을 개선하기 위해 3-1-4 예제에서 Promise를 사용하고 있다.
function 과장님사인받기(서류) { const 과장님사인받은서류 = `과장님 사인 받은 ${서류}`; return Promise.resolve(과장님사인받은서류); } function 부장님사인받기(과장님사인받은서류) { const 부장님사인받은서류 = `부장님 사인 받은 ${과장님사인받은서류}`; setTimeout(() => { return Promise.resolve(부장님사인받은서류); }, 2000); } function 대표님사인받기(부장님사인받은서류) { const 대표님사인받은서류 = `대표님 사인 받은 ${부장님사인받은서류}`; return Promise.resolve(대표님사인받은서류); } function 이모티콘발매(서류) { const 결과 = "개발자 이모티콘 발매"; console.log(결과); } function 회사결재(서류) { return 과장님사인받기(서류) .then(과장님사인받은서류 => 부장님사인받기(과장님사인받은서류)) .then(부장님사인받은서류 => 대표님사인받기(부장님사인받은서류)) .then(대표님사인받은서류 => 이모티콘발매(대표님사인받은서류)); } 회사결재('서류')
예제 3-1-4
위의 과정들을 제너레이터를 통해 3-1-1 예제와 동일하게 만들어 볼 수 있다.
function 과장님사인받기(서류) { const 과장님사인받은서류 = `과장님 사인 받은 ${서류}`; return 과장님사인받은서류; } function 부장님사인받기(서류) { const 부장님사인받은서류 = `부장님 사인 받은 ${서류}`; return 부장님사인받은서류; } function 대표님사인받기(서류) { const 대표님사인받은서류 = `대표님 사인 받은 ${서류}`; return 대표님사인받은서류; } function 이모티콘발매(서류) { const 결과 = "개발자 이모티콘 발매"; return 결과; } function* 회사결재(서류) { const 과장님사인받은서류 = yield 과장님사인받기(서류); const 부장님사인받은서류 = yield 부장님사인받기(과장님사인받은서류); const 대표님사인받은서류 = yield 대표님사인받기(부장님사인받은서류); const 이모티콘발매결과 = 이모티콘발매(대표님사인받은서류); return 이모티콘발매결과; } const 이모티콘발매단계 = 회사결재('서류'); const 과장님사인받은서류 = 이모티콘발매단계.next(); const 부장님사인받은서류 = 이모티콘발매단계.next(과장님사인받은서류.value); const 대표님사인받은서류 = 이모티콘발매단계.next(부장님사인받은서류.value); const 이모티콘발매결과발표 = 이모티콘발매단계.next(); console.log(이모티콘발매결과발표.value);
예제 3-1-5
하지만 이렇게 작동하려면 누군가 next() 함수를 호출해 주어야 한다. 이렇게 봤을 때 과장님사인받기함수, 부장님사인받기함수, 대표님사인받기 함수에서 이터레이터 함수인 next()를 호출해 주어야 할 것이다. 그렇다면 과장님사인받기, 부장님사인받기, 대표님사인받기 함수는 다른 곳에서 재활용하기 힘들어진다.
const iterator = 회사결재('서류'); iterator.next(); function 과장님사인받기(서류) { const 과장님사인받은서류 = `과장님 사인 받은 ${서류}`; iterator.next(과장님사인받은서류); } function 부장님사인받기(서류) { const 부장님사인받은서류 = `부장님 사인 받은 ${서류}`; iterator.next(부장님사인받은서류); } function 대표님사인받기(서류) { const 대표님사인받은서류 = `대표님 사인 받은 ${서류}`; iterator.next(대표님사인받은서류); } function 이모티콘발매(서류) { const 결과 = "개발자 이모티콘 발매"; console.log(결과); } function* 회사결재(서류) { const 과장님사인받은서류 = yield 과장님사인받기(서류); const 부장님사인받은서류 = yield 부장님사인받기(과장님사인받은서류); const 대표님사인받은서류 = yield 대표님사인받기(부장님사인받은서류); const 이모티콘발매결과 = 이모티콘발매(대표님사인받은서류); return 이모티콘발매결과; }
예제 3-1-6
const iterator = 회사결재(서류); iterator.next(); function 과장님사인받기(서류) { // … iterator.next(result); } function 부장님사인받기(과장님사인받은서류) { // … iterator.next(result); } function 대표님사인받기(부장님사인받은서류) { // … iterator.next(result); }
예제 3-1-7
co(function* 회사결재(서류) { const 과장님사인받은서류 = yield 과장님사인받기(서류); const 부장님사인받은서류 = yield 부장님사인받기(과장님사인받은서류); const 대표님사인받은서류 = yield 대표님사인받기(부장님사인받은서류); return 대표님사인받은서류; }).then(대표님사인받은서류 => { console.log(대표님사인받은서류); });
예제 3-1-8
이러한 단점을 개선하기 위한 방법으로 co 라이브러리를 사용하는데, co 라이브러리를 사용하면 위 예제에서 자동으로 next()를 호출해줄 수 있다.
async function 회사결재(서류) { const 과장님사인받은서류 = await 과장님사인받기(서류); const 부장님사인받은서류 = await 부장님사인받기(과장님사인받은서류); const 대표님사인받은서류 = await 대표님사인받기(부장님사인받은서류); return 대표님사인받은서류; }) const 대표님사인받은서류 = 회사결재(서류); 대표님사인받은서류.then(대표님사인받은서류 => { console.log(대표님사인받은서류); }
예제 3-1-9
최근에는 제너레이터co 라이브러리를 자주 사용하지 않고, 많은 코드에서 async / await 을 사용해서 비동기함수를 동기적으로 처리하고 있다. async / await 에 대한 자세한 내용은 이어지는 챕터에서 살펴볼 수 있다.

Reference