제너레이터란?
:코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수
제너레이터와 일반 함수의 차이점
- 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도할 수 있다.
- 일반 함수를 호출: 제어권이 함수에게 넘어가고 함수 코드를 일괄 실행
→ 함수 호출자는 함수 실행을 제어할 수 없다.
- 제너레이터 함수는 함수 호출자가 함수 실행을 일시 중지시키거나 재개할 수 있다.
⇒ 함수의 제어권을 함수가 독점하는 것이 아니라 함수 호출자에게 양도(yield)할 수 있다.
- 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을 수 있다.
- 일반 함수를 호출: 매개변수를 통해 함수 외부에서 값을 주입, 함수 코드를 일괄 실행하여 결과값을 함수 외부로 반환
→ 함수가 실행되는 동안 함수 외부에서 내부로 값을 전달하여 상태 변경 불가
- 제너레이터 함수는 함수 호출자와 양방향으로 함수 상태를 주고 받을 수 있음.
→ 제너레이터 함수는 함수 호출자에게 상태를 전달할 수 있고, 함수 호출자로부터 상태를 전달 받을 수 있다.
- 제너레이터 함수를 호출하면 제너레이터 객체를 반환
- 일반 함수 호출: 함수 코드를 일괄 실행하고 값을 반환
- 제너레이터 함수 호출: 함수 코드를 실행하는 것이 아니라 이터러블이면서 동시에 이터레이터인 제너레이터 객체를 반환
제너레이터 함수의 정의
제너레이터 함수:
function*
키워드로 선언- 하나 이상의
yield
표현식을 포함: 제너레이터 함수를 멈추거나 다시 시작하게 하는 키워드
//제너레이터 함수 선언문 function* genDecFunc() { yield 1; } //제너레이터 함수 표현식 const genDecFunc = function* () { yield 1; } //제너레이터 메서드 const obj = { * genObjMethod() { yield 1; } } //제더레이터 클래스 메서드 class MyClass = { * genObjMethod() { yield 1; } }
- 애스터리스크(*)의 위치는 function 키워드와 함수 이름 사이라면 어디든 상관 없지만 일관성 유지를 위해 function 뒤에 붙이는 것을 권장
- 화살표 함수 정의 불가능
- new 연산자 생성자 함수로 호출 불가능
제너레이터 객체
:제너레이터 함수 호출 시 제너레이터 객체를 생성해 반환
→ 제너레이터 객체는 이터러블이면서 동시에 이터레이터
function* genFunc() { yield 1; yield 2; yield 3; } const generator = genFunc(); console.log(Symbol.iterator in generator); //true console.log('next' in generator); //true
제너레이터 객체는 next 메서드를 갖는 이터레이터이면서, 이터레이터에는 없는 return, throw메서드를 갖는다.
- next 메서드 호출
: 제너레이터 함수의 yield 표현식까지 코드 블록 실행
→ yield된 값을 value 프로퍼티 값으로, false를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체 반환
- return 메서드 호출
: 인수로 전달받은 값을 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 리절틑 객체를 반환
- throw 메서드 호출
: 인수로 전달받은 에러를 발생시킨다. undefined를 value 프로퍼티 값으로, true를 done 값으로 갖는 이터레이터 리절트 객체를 반환
제너레이터의 일시 중지와 재개
:
yield
키워드와 next
메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할 수 있음.yield
키워드
: 제너레이터 함수의 실행을 일시 중지 시키거나 yield 키워드 뒤에 오는 표현식의 평가 결과를 제너레이터 함수 호출자에게 반환
function* genFunc() { yield 1; yield 2; yield 3; } const generator = genFunc(); console.log(generator.next()); //{value: 1, done: false} console.log(generator.next()); //{value: 2, done: false} console.log(generator.next()); //{value: 3, done: false} console.log(generator.next()); //{value: undefined, done: true}
이터레이터의 next 메서드와 달리 제너레이터 객체의 next 메서드에는 인수를 전달할 수 있다.
인수는 제너레이터 함수의 yield 표현식을 할당받는 변수에 할당
function* genFenc() { const x = yield 1; const y = yield (x + 10); return x + y; } const generator = genFunc(0); let res = generator.next(); console.log(res); //{value: 1, done: false} res = generator.next(10); console.log(res); //{value: 20, done: false} res = generator.next(20); console.log(res); //{value: 30, done: true}
제너레이터의 활용
이터러블의 구현
제너레이터 함수를 사용하면 이터레이션 프로토콜을 준수해 이터러블을 생성하는 방식보다 간단히 이터러블을 구현할 수 있다.
- 이터레이션 프로토콜을 준수하여 무한 피보나치 수열을 생성하는 함수

- 제너레이터 함수를 활용한 무한 피보나치 수열을 생성하는 함수

비동기 처리
제너레이터 함수는 next 메서드와 yield 표현식을 통해 함수 호출자와 함수의 상태를 주고 받을 수 있다.
→ 프로미스를 사용한 비동기 처리를 동기처럼 구현할 수 있음!
//제너레이터 실행기 const async = generatorFunc => { const generator = generatorFunc(); // 2 const onResolved = arg => { const result = generator.next(arg); // 5 return result.done ? result.value // 9 : result.value.then(res => onResolved(res));// 7 } return onResolved; // 3 } (async(function* fetchTodo(){ // 1 const url = 'https://jsonplaceholder.typicode.com/todos/1'; const response = yield fetch(url); //6 const todo = yield response.json(); //8 console.log(todo); //{userId: 1, id: 1, title: "delectus aut autem", completed: false} })()); // 4
async/await
:제너레이터로도 비동기 처리를 동기 처리처럼 구현할 수 있지만 코드가 너무 장황하고 가독성이 나쁘다!
⇒ es8에서 async/await 도입
async/await
: 프로미스 기반으로 동작.
→ 후속처리 메서드 없이 동기 처리처럼 프로미스 처리 결과를 반환
async function fetchTodo() { const url = 'https://jsonplaceholder.typicode.com/todos/1'; const response = await fetch(url); const todo = await response.json(); console.log(todo); //{userId: 1, id: 1, title: "delectus aut autem", completed: false} } fetchTodo();
async 함수
: async 함수는 언제나 프로미스를 반환
- 클래스 constructor 메서드는 async 메서드가 될 수 없다.
→ constructor 메서드는 인스턴스를 반환해야 하지만 async는 프로미스를 반환해야하기 때문!
await 키워드
: await 키워드는 반드시 async 함수 내부에서 사용해야 한다.
- 프로미스가 settled상태(비동기 처리가 수행된 상태)가 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환.
const getGithubUserName = async id => { const res = await fetch(`https://api.github.com/users/${id}`); // 1 const {name} = await res.json(); // 2 console.log(name); // jiyoung }; getGithubUserName('zero');
async/await로 처리 할 때의 특징
async function foo() { const a = await new Promise(resolve => setTimeout(() => resolve(1), 3000)); const b = await new Promise(resolve => setTimeout(() => resolve(2), 2000)); const c = await new Promise(resolve => setTimeout(() => resolve(3), 1000)); console.log([a, b, c]); // [1, 2, 3]; }; foo(); //약 6초가 소요된다
위의 예제에서 수행하는 3개의 비동기 처리는 서로 연관 없이 개별적으로 수행되므로 순차적으로 처리할 필요가 없다. 따라서 다음과 같이 수정할 수 있다.
async function foo() { const res = await Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), new Promise(resolve => setTimeout(() => resolve(2), 2000)), new Promise(resolve => setTimeout(() => resolve(3), 1000)) ]) console.log(res); // [1, 2, 3]; }; foo(); //약 3초가 소요된다.
하지만 앞선 비동기 처리의 결과를 가지고 수행해야 한다면 await를 사용해야 한다.
에러처리
비동기 처리를 위한 콜백패턴의 단점은 에러 처리가 곤란하다는 것!
async/await에서 에러처리는 try…catch 문을 사용할 수 있다.
const foo = async() => { try{ const wrongUrl = 'https://wrong.url'; const response = await fetch(wrongUrl); const data = await response.json(); console.log(data); } catch(err) { console.err(err); //TypeError: Failed to fetch } }; foo();
만약 async 함수 내에서 catch 문으로 에러처리를 하지 않는다면?
: 발생한 에러를 reject하는 프로미스를 반환한다.
⇒
Promise.prototype.catch
후속 처리 메서드를 사용해서 에러를 캐치const foo = async() => { const wrongUrl = 'https://wrong.url'; const response = await fetch(wrongUrl); const data = await response.json(); return data; }; foo() .then(console.log) .catch(console.error); //TypeError: Failed to fetch