JS-제너레이터
| topics | 200-프론트개발 |
| types | 이론 학습 |
| tags | |
| references | ko.javascript.info/generators |
제너레이터는 왜 쓸까
일반 함수는 하나의 값만 return한다. 근데 제너레이터는 여러 개의 값을 하나씩 반환(yield)할 수 있다.
기본 사용법
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
const generator = generateSequence();
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}
function* 문법으로 정의하고, yield로 값을 하나씩 반환한다.
언제 쓸까
1. 무한 시퀀스 생성
배열로 만들면 메모리가 터지는데, 제너레이터는 필요할 때만 값을 생성한다.
function* infiniteSequence() {
let num = 0;
while (true) {
yield num++;
}
}
const sequence = infiniteSequence();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
// 무한히 계속...
2. 이터러블 객체 생성
for...of로 순회 가능한 객체를 쉽게 만들 수 있다.
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for (let num of range(1, 5)) {
console.log(num); // 1, 2, 3, 4, 5
}
3. 비동기 처리 (과거에)
요즘은 async/await을 쓰지만, 과거에는 제너레이터로 비동기를 동기처럼 작성했다.
function* fetchData() {
const data = yield fetch('https://api.example.com/data');
console.log(data);
}
const gen = fetchData();
const result = gen.next();
result.value
.then(response => response.json())
.then(data => gen.next(data));
지금은:
async/await을 사용하자. 제너레이터로 비동기 처리하는 건 복잡하다.
제너레이터 컴포지션
제너레이터 안에서 다른 제너레이터를 호출할 수 있다.
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
yield* generateSequence(48, 57); // 0..9
yield* generateSequence(65, 90); // A..Z
yield* generateSequence(97, 122); // a..z
}
console.log([...generatePasswordCodes()]);
// [48, 49, 50, ..., 122] - 총 62개
yield*로 다른 제너레이터에게 실행을 위임한다.
양방향 통신
제너레이터는 값을 받을 수도 있다!
function* gen() {
let ask1 = yield "2 + 2 = ?";
console.log(ask1); // 4
let ask2 = yield "3 * 3 = ?";
console.log(ask2); // 9
}
const generator = gen();
console.log(generator.next().value); // "2 + 2 = ?"
console.log(generator.next(4).value); // "3 * 3 = ?"
console.log(generator.next(9).done); // true
next(value)로 값을 전달하면, 그 값이 yield의 결과가 된다.
장점과 단점
장점
- 메모리 효율적 - 필요할 때만 값을 생성 (lazy evaluation)
- 무한 시퀀스 가능 - 무한 루프도 메모리 문제 없음
- 실행 제어 - 실행을 멈추고 재개할 수 있음
- 이터러블 생성 -
for...of로 쉽게 순회
단점
- 복잡함 - 일반 함수보다 이해하기 어려움
- 디버깅 어려움 - 실행 흐름을 따라가기 힘듦
- 성능 - 간단한 작업에는 오히려 느릴 수 있음
실전 사용 예시
ID 생성기
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
const getId = idGenerator();
console.log(getId.next().value); // 1
console.log(getId.next().value); // 2
console.log(getId.next().value); // 3
페이지네이션
function* paginate(items, pageSize) {
for (let i = 0; i < items.length; i += pageSize) {
yield items.slice(i, i + pageSize);
}
}
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const pages = paginate(items, 3);
console.log(pages.next().value); // [1, 2, 3]
console.log(pages.next().value); // [4, 5, 6]
console.log(pages.next().value); // [7, 8, 9]
console.log(pages.next().value); // [10]
제너레이터 vs async/await
과거에는 제너레이터로 비동기를 처리했지만, 지금은 async/await을 쓴다.
// 과거 - 제너레이터
function* fetchUser() {
const user = yield fetch('/api/user').then(r => r.json());
const posts = yield fetch(`/api/posts/${user.id}`).then(r => r.json());
return {user, posts};
}
// 현재 - async/await (훨씬 간단!)
async function fetchUser() {
const user = await fetch('/api/user').then(r => r.json());
const posts = await fetch(`/api/posts/${user.id}`).then(r => r.json());
return {user, posts};
}
결론: 비동기 처리는
async/await, 이터레이션이나 무한 시퀀스는 제너레이터를 사용하자.
관련 문서
- JS-함수 심화학습 - 함수 관련 심화 내용
- JS-this - this 바인딩