46. 제너레이터와 async/await
46.1 제너레이터란?
- 코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할수 있는 특수한 함수
1. 제너레이터와 일반 함수의 차이
- 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도할 수 있음
- 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을 수 있음
- 제너레이터 함수를 호추하면 제너레이터 객체를 반환함
46.2. 제너레이터 함수의 정의
- 제너레이터 함수는 function* 키워드로 선언하고, 하나 이상의 yield 표현식을 포함함
// 제너레이터 함수 선언문
function* genDecFunc() {
yield 1;
};
// 제너레이터 함수 표현식
const genExpFunc = function* () {
yield 1;
};
// 제너레이터 메서드
const obj = {
* genObjMethod() {
yield 1;
}
};
// 제너레이터 클래스 메서드
class MyClass {
* genClsMethod() {
yield 1;
}
}
- 애스터리스크(*)의 위치는 function키워드와 함수 이름 사이라면 어디든지 상관없지만 일관성 유지를 위해 function키워드 바로 뒤에 붙이는 것을 권장함
- 제너레이터 함수는 화살표 함수와 new연산자와 함께 생성자 함수로 호출할 수 없음
46.3 제너레이터 객체
- 제너레이터 함수를 호출하면 제너레이터 객체를 생성해 반환
- 제너레이터 객체는 next메서드를 갖는 이터레이터이지만, 이터레이터에는 없는 return, throw메서드를 가짐
- next메서드를 호출하면 제너레이터 함수의 yield표현식까지 코드 블록을 실행하고 yield된 값을 value프로퍼티 값으로, false를 done프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환함
- return메서드를 호출하면 인수로 전달받은 값을 value프로퍼티 값으로, true를 done프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환함
- throw메서드를 호출하면 인수로 전달받은 에러를 발생시키고, undefined를 value프로퍼티 값으로, true를 done프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환
function* genFunc() {
try {
yield 1;
yield 2;
yield 3;
} catch (e) {
console.error(e);
}
}
const generator = genFunc();
// return메서드를 활용
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.return('End!')); // {value: 'End!', done: true}
// throw메서드를 활용
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.throw('Error!')); // {value: undefined, done: true}
46.4 제너레이터의 일시 중지와 재개
- 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메서드에 전달한 인수는 제너레이터 함수의 yield표현식을 할당받는 변수에 할당됨
function* genFunc() {
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}
let res = generator.next(10);
console.log(res); {value: 20, done: false}
46.5 제너레이터의 활용
46.5.1 이터러블의 구현
- 제너레이터 함수를 사용하면 간단히 이터러블을 구현할 수 있음
- 예시) 이터레이션 프로토콜을 준수하여 무한 피보나치 수열을 생성하는 함수 구현
const infiniteFibonacci = (function () {
let [pre, cur] = [0, 1];
return {
[Symbol.iterator]() { return this; },
next() {
[pre, cur] = [cur, pre + cur];
return {value: cur};
}
};
}());
for (const num of infiniteFibonacci) {
if (num > 10000) break;
console.log(num); // 1 2 3 5 8...
}
const infiniteFibonacci = (function* () {
let [pre, cur] = [0, 1];
while (true) {
[pre, cur] = [cur, pre + cur];
yield cur;
}
}());
for (const num of infiniteFibonacci) {
if (num > 10000) break;
console.log(num); // 1 2 3 5 8...
}
46.6 async/await (ES8)
- async/await는 프로미스를 기반으로 동작하고, 후속 처리할 필요 없이 마치 동기 처리처럼 프로미스를 사용할 수 있음
const fetch = require('node-fetch');
async function fetchTodo() {
const url = 'https://jsonplaceholder.typicode.com/todos/1';
const response = await fetch(url);
const todo = await response.json();
console.log(todo);
}
fetchTodo();
46.6.1. async함수
- async함수는 async키워드를 사용해 정의하며, 언제나 프로미스를 반환함
46.6.2 await 키워드
- await키워드는 반드시 프로미스 앞에서 사용해야 하고, 프로미스가 settled상태가 될 때까지 대기하다가 settled상태가 되면 프로미스가 resolve한 처리 결과를 반환함
46.6.3 에러처리
- async/await는 try...catch문을 사용하여 에러 처리를 할 수 있음
- async함수 내에서 catch문을 통해서 에러 처리를 하지 않으면 async함수는 발생한 에러를 reject하는 프로미스를 반환함