46장 제너레이터와 async/await

46.1 제너레이터란?

46.2 제너레이터 함수의 정의

// 제너러/이터 함수 선언문
function* genDecFunc() {
    yield 1;
}
// 제너레이터 함수 표현식
const genExpFunc = function* () {
    yield 1;
};
// 제너레이터 메서드
const obj = {
    * genObjMethod() {
        yield 1;
    }
}
// 제너레이터 클래스 메서드
class MyClass {
    * genClsMethod() {
        yield 1;
    }
}

46.3 제너레이터 객체

// 제너레이터 함수
function* genFunc() {
    yield 1;
    yield 2;
    yield 3;
}
// 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다.
const generator = genFunc();
// 제너레이터 객체는 이터러블이면서 동시에 이터레이터다.
// 이터러블은 Symbol, iterator 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 객체다.
console.log(Symbol.iterator in generator); // true
// 이터레이터는 next 메서드를 갖는다.
console.log('next' in generator); // true
function* genFunc() {
    try {
        yield 1;
        yield 2;
        yield 3;
    } catch (e) {
        console.error(e);
    }
}
const generator = genFunc();
console.log(generator.next()); // {value: 1, done: false}
console.log(generator.return('End I')); // {value: "End!", done: true}
console.log(generator.throw('Error!')); // {value: undefined, done: true}

46.4 제너레이터의 일시 중지와 재개

function* genFunc() {
    // 처음 next 메서드를 호출하면 첫 번째 yield 표현식까지 실행되고 일시 중지된다.
    // 이때 yield된 값 1은 next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에 할당된다.
    // x 변수에는 아직 아무것도 할당되지 않았다. x 변수의 값은 next 메서드가 두 번째 호출될 때 결정된다.
    const x = yield 1;
    // 두 번째 next 메서드를 호출할 때 전달한 인수 10은 첫 번째 yield 표현식을 할당받는
    // x 변수에 할당된다. 즉, const x = yield 1;은 두 번째 next 메서드를 호출했을 때 완료된다.
    // 두 번째 next 메서드를 호출하면 두 번째 yield 표현식까지 실행되고 일시 중지된다.
    // 이때 yield된 값 x + 10은 next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에 할당된다.
    const y = yield (x + 10);
    // 세 번째 next 메서드를 호출할 때 전달한 인수 20은 두 번째 yield 표현식을 할당받는 y 변수에 할당된다.
    // 즉, const y = yield (x + 10);는 세 번째 next 메서드를 호출했을 때 완료된다.
    // 세 번재 next 메서드를 호출하면 함수 끝까지 실행된다.
    // 이때 제너레이터 함수의 반환값 x + y는 next 메서드가 반환한 이터레이터 리절트 객체의
    // value 프로퍼티에 할당된다. 일반적으로 제너레이터의 반환값은 의미가 없다.
    // 따라서 제너레이터어/서는 값을 반환할 필요가 없고 return은 종료의 의미로만 사용해야 한다.
    return x + y;
}
// 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다.
// 이터러블이며 동시에 이터레이터인 제너레이터 체는 next 메서드를 갖는다.
const generator = genFunc(0);
// 처음 호출하는 next 메서드에는 인수를 전달하지 않는다.
// 만약 처음 호출하는 next 메서드에 인수를 전달하면 무시된다.
// next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에는 첫 번째 yield된 값 10이 할당된다.
let res = generator.next();
console.log(res); // {value: 1, done: false}
// next 메서드에 인수로 전달한 10은 genFunc 함수의 x 변수에 할당된다.
// next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에는 두 번째 yield된 값 20이 할당된다.
res = generator.next(10);
console.log(res); // {value: 20, done: false}
// next 메서드에 인수로 전달한 20은 genFunc 함수의 y 변수에 할당된다.
// next 메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티에는 제너레이터 함수의 반환값 30이 할당된다.
res = generator.next(20);
console.log(res); // {value: 30, done: true}

46.5 제너레이터의 활용

46.5.1 이터러블의 구현
// 무한 이터러블을 생성하는 제너레이터 함수
const infiniteFibonacci = (function* () {
    let [pre, cur] = [0, 1];
    while (true) {
    [pre, cur] = [cur, pre + cur];
    yield cur;
    }
}())
// infiniteFibonacci는 무한 이터러블이다.
for (const num of infiniteFibonacci) {
    if (num > 10000) break;
    console.log(num); // 1 2 3 5 8... 2584 4181 6765
}
46.5.2 비동기 처리
// 제너레이터 실행기
const async = generatorFunc => {
    const generator = generatorFunc(); // ②
    const onResolved = arg => {
        const result = generator.next(arg); // ⑤
        return result.done
        ? result.value // ⑨
        : result.value.then(res => onResolved(res)); // ⑦
    }
    return onResolved; // ③
}
(async(function* fetchTodo() { // ①
    const url = 'https://jsonplaceholder.typicode.eom/todos/1';
    const response = yield fetch(url); // ⑥
    const todo = yield response.json(); // ⑧
    console.log(todo);
    // {userid: 1, id: 1, title: 'delectus aut autem', completed: false}
})()) // ④

1) async 함수가 호출(①)되면 인수로 전달받은 제너레이터 함수 fetchTodo를 호출하여 제너레이터 객체를 생성(②)하고 onResolved 함수를 반환(③)한다. onResolved 함수는 상위 스코프의 generator 변수를 기억하는 클로저다. async 함수가 반환한 onResolved 함수를 즉시 호출(④)하여 ②에서 생성한 제너레이터 객체의 next 메서드를 처음 호출(⑤)한다.
2) next 메서드가 처음 호출(⑤)되면 제너레이터 함수 fetchTodo의 첫 번째 yield 문(⑥)까지 실행된다. 이때 next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 false, 즉 아직 제너레이터 함수가 끝까지 실행되지 않았다면 이터레이터 리절트 객체의 value 프로퍼티 값 즉 첫 번째 yield된 fetch 함수가 반환한 프로미스가 resolve한 Response 객체를 onResolved 함수에 인수로 전달하면서 재귀 호출(⑦)한다. 3) onResolved 함수에 인수로 전달된 Response 객체를 next 메서드에 인수로 전달하면서 next 메서드를 두 번째로 호출(⑤)한다. 이때 next 메서드에 인수로 전달한 Response 객체는 제너레이터 함수 fetchTodo의 response 변수(⑥)에 할당되고 제너레이터 함수 fetchTodo의 두 번째 yield 문(⑧)까지 실행된다. 4) next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 false, 즉 아직 제너레이터 함수 fetchTodo가 끝까지 실행되지 않았다면 이터레이터 리절트 객체의 value 프로퍼티 값 즉 두 번째 yield된 response, json 메서드가 반환한 프로미스가 resolve한 todo 객체를 onResolved 함수에 인수로 전달하면서 재귀 호출(⑦)한다. 5) onResolved 함수에 인수로 전달된 todo 객체를 next 메서드에 인수로 전달하면서 next 메서드를 세 번째로 호출(⑤)한다.이때 next 메서드에 인수로 전달한 todo 객체는 제너레이터 함수 fetchTodo의 todo 변수(⑧)에 할당되고 제너레이터 함수 fetchTodo가 끝까지 실행된다. 6) next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 true, 즉 제너레이터 함수 fetchTodo가 끝까지 실행되었다면 이터레이터 리절트 객체의 value 프로퍼티 값 즉 제너레이터 함수 fetchTodo의 반환값인 undefined를 그대로 반환(⑨)하고 처리를 종료한다.

46.6 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 autem1, completed: false}
}
fetchTodo();
46.6.1 async 함수
// async 함수 선언문
async function foo(n) { return n; }
foo(l).then(v => console.log(v)); // 1
// async 함수 표현식
const bar = async function (n) { return n; };
bar(2).then(v => console.log(v)); // 2
// async 화살표 함수
const baz = async n => n;
baz(3).then(v => console.log(v)); // 3
// async 메서드
const obj = {
    async foo(n) { return n; }
}
obj.foo(4).then(v => console.log(v)); // 4
// async 클래스 메서드
class MyClass {
    async bar(n) { return n; }
}
const myClass = new MyClass();
myClass.bar(5).then(v => console.log(v)); // 5
46.6.2 await 키워드
const fetch = require('node-fetch');
const getGithubllserName = async id => {
    const res = await fetch('https://api.github.com/users/${id}'); // ①
    const { name } = await res.json(); // ②
    console.log(name); // Ungmo Lee
}
getGithubUserName(ungmo21);
async function foo() {
    const a = await new Promise(resolve => setTimeout(() => resolve(l), 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초 소요된다.
async function foo() {
    const res = await Promise.all([
    new Promise(resolve => setTimeout(() => resolve(l), 3000)),
    new Promise(resolve => setTimeout(() => resolve(2), 2000)),
    new Promise(resolve => setTimeout(() => resolve(3), 1000))
    ])
    console.log(res); // [1, 2, 3]
}
foo(); // 약 3초 소요된다.
46.6.3 에러 처리
const fetch = require('node-fetch');
const foo = async () => {
    try {
        const wrongUrl = 'https://wrong.url';
        const response = await fetch(wrongllrl);
        const data = await response.json();
        console.log(data);
    } catch (err) {
        console, error(err); // TypeError: Failed to fetch
    }
}
foo();

끝!