JavaScript generators (function*) คือฟังก์ชันพิเศษที่สามารถหยุดชั่วคราวและดำเนินการต่อได้โดยใช้ yield
พื้นฐาน Generator: function* และ yield
ฟังก์ชัน generator ส่งคืน generator object ที่ implements Iterator protocol
// Generator function syntax: function* with yield
function* counter(start = 0) {
let i = start;
while (true) {
yield i++; // pauses here, returns i, resumes on next()
}
}
const gen = counter(1);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
// Finite generator
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
// for...of automatically calls .next() and stops at done: true
for (const num of range(0, 10, 2)) {
console.log(num); // 0, 2, 4, 6, 8
}
// Spread operator works with generators
const nums = [...range(1, 6)]; // [1, 2, 3, 4, 5]
// Destructuring works too
const [a, b, c] = range(10, 20); // a=10, b=11, c=12การสื่อสารสองทาง: yield เป็น Expression
yield ไม่ใช่แค่ return มันเป็น expression ที่รับค่าผ่าน next(value)
// Generators support two-way communication
// yield receives values via next(value)
function* calculator() {
let result = 0;
while (true) {
const input = yield result; // pauses and sends result; receives input
if (input === null) break;
result += input;
}
return result;
}
const calc = calculator();
calc.next(); // start: { value: 0, done: false }
calc.next(10); // add 10: { value: 10, done: false }
calc.next(5); // add 5: { value: 15, done: false }
calc.next(null); // stop: { value: 15, done: true }
// Generator as stateful iterator
function* idGenerator(prefix = 'id') {
let id = 1;
while (true) {
const reset = yield `${prefix}-${id}`;
if (reset) {
id = 1;
} else {
id++;
}
}
}
const ids = idGenerator('user');
console.log(ids.next().value); // 'user-1'
console.log(ids.next().value); // 'user-2'
console.log(ids.next(true).value); // 'user-1' (reset)
console.log(ids.next().value); // 'user-2'yield*: การมอบหมายให้ Iterables อื่น
yield* มอบหมายให้ iterable อื่น
// yield* — delegate to another iterable
function* innerGen() {
yield 'a';
yield 'b';
yield 'c';
}
function* outerGen() {
yield 1;
yield* innerGen(); // delegate: yields 'a', 'b', 'c'
yield* [4, 5, 6]; // works with any iterable
yield 7;
}
console.log([...outerGen()]); // [1, 'a', 'b', 'c', 4, 5, 6, 7]
// Practical: flatten nested arrays
function* flatten(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flatten(item); // recursive delegation
} else {
yield item;
}
}
}
const nested = [1, [2, [3, 4], 5], [6, 7]];
console.log([...flatten(nested)]); // [1, 2, 3, 4, 5, 6, 7]
// Tree traversal with yield*
function* walkTree(node) {
yield node.value;
for (const child of node.children ?? []) {
yield* walkTree(child); // depth-first traversal
}
}Async Generators: การ Streaming ข้อมูล
Async generators รวม generators กับ async/await สำหรับการ streaming
// Async generators: async function* with yield
async function* streamLines(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) {
if (buffer) yield buffer;
break;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
yield line; // yield each complete line
}
}
}
// Usage with for await...of
async function processCSV(url) {
let lineNumber = 0;
for await (const line of streamLines(url)) {
lineNumber++;
if (lineNumber === 1) continue; // skip header
const [name, score] = line.split(',');
console.log(`${name}: ${score}`);
}
}
// SSE (Server-Sent Events) as async generator
async function* sseStream(url) {
const response = await fetch(url);
const reader = response.body.getReader();
const decoder = new TextDecoder();
for await (const chunk of readChunks(reader)) {
const text = decoder.decode(chunk);
for (const line of text.split('\n')) {
if (line.startsWith('data: ')) {
yield JSON.parse(line.slice(6));
}
}
}
}รูปแบบในโลกจริง
Generators ช่วยให้สร้าง lazy pipelines ที่ประมวลผลเฉพาะข้อมูลที่จำเป็น
// Real-world: infinite scroll with generator
function* paginator(fetchPage) {
let page = 1;
let hasMore = true;
while (hasMore) {
const { items, totalPages } = yield fetchPage(page);
hasMore = page < totalPages;
page++;
}
}
// Lazy pipeline with generators
function* map(iterable, fn) {
for (const item of iterable) {
yield fn(item);
}
}
function* filter(iterable, predicate) {
for (const item of iterable) {
if (predicate(item)) yield item;
}
}
function* take(iterable, n) {
let count = 0;
for (const item of iterable) {
if (count++ >= n) break;
yield item;
}
}
// Lazy pipeline — no intermediate arrays created!
const first10EvenSquares = [
...take(
filter(
map(range(1, Infinity), x => x * x),
x => x % 2 === 0
),
10
)
];
// [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]
// Observable-like: cancelable async iteration
async function* withTimeout(asyncIterable, timeoutMs) {
const timeout = setTimeout(() => {
throw new Error('Stream timed out');
}, timeoutMs);
try {
for await (const item of asyncIterable) {
yield item;
}
} finally {
clearTimeout(timeout);
}
}Generators vs ทางเลือก
| Feature | Generator | async/await | Promise | Observable |
|---|---|---|---|---|
| Infinite sequences | Perfect | No | No | Yes |
| Lazy evaluation | Yes (pull-based) | No | No | Yes (push) |
| Backpressure | Natural (pull) | No | No | Yes |
| Streaming async | async function* | No | No | Yes (RxJS) |
| Two-way comms | yield expression | No | No | No |
| Browser support | ES2015+ (all) | ES2017+ | ES2015+ | Requires RxJS |
แนวปฏิบัติที่ดีที่สุด
- ใช้ generators สำหรับ lazy sequences async/await สำหรับ async ครั้งเดียว
- ใช้ return สำหรับออกก่อน try/finally สำหรับ cleanup
- Async generators เหมาะกับ HTTP streaming, SSE, WebSocket
- สร้าง pipeline helpers ที่ใช้ซ้ำได้ (map, filter, take)
- TypeScript: annotate ด้วย Generator type
คำถามที่พบบ่อย
Generator กับ Iterator ต่างกันอย่างไร?
Iterator คือ object ที่มี next() Generator คือฟังก์ชันที่สร้างและจัดการ iterator อัตโนมัติ
Generators สามารถแทนที่ async/await ได้ไหม?
สำหรับ operation ครั้งเดียว ไม่ได้ สำหรับ streaming async generators ดีกว่า
Iterator Protocol คืออะไร?
Object ที่มี next() คืน { value, done } และ [Symbol.iterator]() สำหรับ iterable
Generators เหมาะกับ Redux-Saga ไหม?
ใช่ Redux-Saga ใช้สำหรับ effects ที่ test ได้
จะยกเลิก generator ได้อย่างไร?
เรียก generator.return(value) หรือใช้ break ใน for...of