DevToolBoxFREE
BlogAdvertise

JavaScript GeneratorsとIterators:2026年完全ガイド

12分by DevToolBox

JavaScriptジェネレーター(function*)はyieldキーワードで一時停止・再開できる特殊な関数です。

ジェネレーターの基本:function*とyield

ジェネレーター関数はIteratorプロトコルを実装するジェネレーターオブジェクトを返します。

// 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

yieldはreturn文だけでなく、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*:他のiterableへの委譲

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/awaitを組み合わせてストリーミングに使います。

// 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));
            }
        }
    }
}

実際のパターン

ジェネレーターは必要なデータだけを処理するレイジーパイプラインを実現します。

// 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);
    }
}

ジェネレーター vs 代替手段

FeatureGeneratorasync/awaitPromiseObservable
Infinite sequencesPerfectNoNoYes
Lazy evaluationYes (pull-based)NoNoYes (push)
BackpressureNatural (pull)NoNoYes
Streaming asyncasync function*NoNoYes (RxJS)
Two-way commsyield expressionNoNoNo
Browser supportES2015+ (all)ES2017+ES2015+Requires RxJS

ベストプラクティス

  • レイジーシーケンスと無限データにジェネレーター、一回限りの非同期にasync/await。
  • returnで早期終了、try/finallyでクリーンアップ。
  • 非同期ジェネレーターはHTTPストリーミング、SSE、WebSocketに最適。
  • 再利用可能なパイプラインヘルパー(map、filter、take)をジェネレーター関数で作成。
  • TypeScript:Generator型でアノテーション。

よくある質問

ジェネレーターとイテレーターの違いは?

イテレーターはnext()を持つオブジェクト。ジェネレーターはイテレーターを自動的に作成・管理する関数。

ジェネレーターはasync/awaitを置き換えられる?

一回限りには不向き。ストリーミングには非同期ジェネレーターが優れる。

Iteratorプロトコルとは?

{ value, done }を返すnext()を持つオブジェクト、[Symbol.iterator]()を持つIterable。

ジェネレーターはRedux-Sagaに向いている?

はい — Redux-Sagaはテスト可能なエフェクトにジェネレーターを使用。

ジェネレーターをキャンセルするには?

generator.return(value)を呼ぶか、for...ofでbreakを使う。

𝕏 Twitterin LinkedIn
この記事は役に立ちましたか?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy