일반 함수 | 제네레이터 | |
---|---|---|
함수의 제어권 | 함수를 호출하면 제어권이 함수에게 넘어가므로, 함수 호출자가 함수 실행 제어 X | 제네레이터 함수는 함수 호출자가 함수 실행을 제어 O |
함수 호출자와 함수 상태 | 함수가 실행되고 있는 동안에는 함수 외부에서 내부로 값을 전달하여 상태 변경 X | 제네레이터 함수는 함수 호출자에게 상태를 전달하기 O, 함수 호출자로부터 상태 전달받기 O |
반환값 | 함수를 호출하면 함수 코드를 실행하고 값을 반환함 | 제네레이터 함수를 호출하면 제네레이터 객체를 반환함 |
function*
키워드로 선언함*
의 위치는 function 키워드와 함수 이름 사이의 어디든지 상관없음function* genFunc() {yield 1; } // 권장
function * genFunc() {yield 1; }
function *genFunc() {yield 1; }
function*genFunc() {yield 1; }
// 제네레이터 함수 선언문
function* genDecFunc() {
yield 1;
}
// 제네레이터 함수 표현식
const genExpFunc = function* () => {
yield 1;
}
// 제네레이터 메서드
const obj = {
* genObjMethod() {
yield 1;
}
}
// 제네레이터 클래스 메서드
class MyClass {
* genClsMethod() {
yield 1;
}
}
const genArrowFunc = * () => {
yield 1;
}; // SyntaxError : Unexpected token '*'
function* genFunc() {
yield 1;
}
new genFunc(); // TypeError: genFunc is not a constructor
제네레이터 함수를 호출하면 제네레이터 객체를 생성해 반환함
이터러블(iterable)과 이터레이터(iterator)
Note
- 이터러블 프로토콜(iterable protocol)
- Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 Symbol.iterator 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환함
- 이터레이터 프로토콜(iterator protocol)
- 이터레이터는 next 메서드를 소유하며 next 메서드를 호출하면 이터러블을 순회하며 value와 done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환함
next 메서드를 호출할 경우,
return 메서드를 호출할 경우,
throw 메서드를 호출할 경우,
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!')); // {value: 'End!', done: true}
console.log(generator.throw('Error!'); // {value: undefined, done: true}
제네레이터는 yield 키워드와 next 메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할 수 있음
next 메서드를 반복 호출하여 제네레이터 함수가 끝까지 실행될 경우, 아래와 같은 값의 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환함
function* genFunc() {
yield 1;
yield 2;
yield 3;
}
const generator = genFunc();
// 처음 next 메서드를 호출하면, 첫 번째 yield 표현식까지 실행되고 일시 중지됨
console.log(generator.next()); // {value: 1, done: false}
// 다시 next 메서드를 호출하면, 두 번째 yield 표현식까지 실행되고 일시 중지됨
console.log(generator.next()); // {value: 2, done: false}
// 다시 next 메서드를 호출하면, 세 번째 yield 표현식까지 실행되고 일시 중지됨
console.log(generator.next()); // {value: 3, done: false}
// 다시 next 메서드를 호출하면, 남은 yield 표현식이 없으므로 제네레이터 함수가 마지막까지 실행됨
console.log(generator.next()); // {value: undefined, done: true}
function* genFunc() {
// 처음 next 메서드를 호출하면, 첫 번째 yield 표현식까지 실행되고 일시 중지됨
// yield된 1은 이터레이터 리절트 객체의 value 프로퍼티에 할당됨
// x 변수에는 아직 할당 안됨, x 변수의 값은 next가 두 번째 호출될 때 결정됨
const x = yield 1;
// 두 번째 next 메서드를 호출할 때 전달된 인수 10은 첫 번째 yield 표현식을 할당받는 x에 할당됨
// 두 번째 next 메서드를 호출하면, 두 번째 yield 표현식까지 실행되고 일시 중지됨
// 이 때 yield된 값 x + 10은 이터레이터 객체의 value 프로퍼티에 할당됨
const y = yield (x + 10);
// 세 번째 next 메서드를 호출할 때 전달된 인수 20은 두 번째 yield 표현식을 할당받는 변수 y에 할당됨
// 세번 째 next 메서드를 호출하면, 남아있는 yield 표현식이 없으므로 함수 끝까지 실행됨
// 함수의 반환값 x + y는 이터레이터 객체의 value 프로퍼티에 할당됨
return x + y;
}
const generator = genFunc();
// 처음 호출하는 next 메서드에는 인수를 전달하지 않음
// 만약 처음 호출하는 next 메서드에 인수를 전달하면 무시됨
// 이터레이터 리절트 객체의 value 프로퍼티에 첫 번째 yield된 값 1이 할당됨
let rest = generator.next();
console.log(rest); // {value: 1, done: false}
// next 메서드에 인수로 전달한 10은 genFunc 함수의 x 변수에 할당됨
// 이터레이터 리절트 객체의 value 프로퍼티에 두 번째 yield된 값 20이 할당됨
let rest = generator.next(10);
console.log(rest); // {value: 20, done: false}
// next 메서드에 인수로 전달된 20은 genFunc 함수의 y 변수에 할당됨
// 이터레이터 리절트 객체의 value 프로퍼티에 genFunc 함수의 반환값 30이 할당됨
let rest = generator.next();
console.log(rest); // {value: 30, done: true}
// 방법 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 8 13 ... 2584 4181 6765
}
// 방법 2 : 제네레이터를 이용한 방식
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 8 13 ... 2584 4181 6765
}
// node-fetch : Node.js 환경에서 window.fetch 함수를 사용하기 위한 패키지
// 브라우저 환경에서 이 예제를 실행한다면 아래 코드는 필요 없음
const fetch = require('node-fetch');
// 제네레이터 실행기
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.com/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}
})());
위 예제에 대한 설명
제네레이터 실행기가 필요하다면 직접 구현하는 것보다 co 라이브러리를 사용하는 것을 권장함
const fetch = require('node-fetch');
const co = require('co');
co(function* fetchTodo() {
const url = 'https://jsonplaceholder.typicode.com/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}
});
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); // {userId: 1, id: 1, title: 'delectus aut autem', completed: false}
};
// async 함수 선언문
async function foo(n) { return n; }
foo(1).then(v => console.log(v)); // 1
// async 함수 표현식
const foo = async function (n) { return n; };
foo(1).then(v => console.log(v)); // 1
// async 화살표 함수
const foo = async n => n;
foo(1).then(v => console.log(v)); // 1
// 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
const fetch = require('node-fetch');
const getGithubUserName = async (id) => {
const res = await fetch(`https://api.github.com/users/${id}`);
const { name } = await res.json();
console.log(todo); // Ungmo Lee
};
getGithubUserName('ungmo2');
위 예제 설명
여러 개의 비동기 처리할 때 await 권장 방법
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초 소요됨
async function bar(n) {
const a = await new Promise(resolve => setTimeout(() => resolve(n), 3000));
// 두 번째 비동기 처리를 수행하려면, 첫 번째 비동기 처리 결과가 필요함
const b = await new Promise(resolve => setTimeout(() => resolve(a + 1), 2000));
// 세 번째 비동기 처리를 수행하려면, 두 번째 비동기 처리 결과가 필요함
const c = await new Promise(resolve => setTimeout(() => resolve(b + 1), 1000))
console.log(res); // [1, 2, 3]
}
bar(1); // 약 6초 소요됨
const fetch = require('node-fetch');
async function fetchTodo() {
try {
const wrongUrl = 'https://wrong.url';
const response = await fetch(wrongUrl);
const data = await response.json();
console.log(data);
} catch (err) {
console.error(err); // TypeError: Failed to fetch
}
};
const fetch = require('node-fetch');
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