0Pricing
Node.js Backend Development Bootcamp · 강의

스트림에서 비동기 반복자 및 for-await-of 사용하기

비동기 반복으로 읽기 가능 스트림을 편리하게 소비하고 스트림과 비동기 생성기 사이를 변환합니다.

스트림에서 비동기 반복자 및 for-await-of 사용하기은(는) CoddyKit의 무료 Node.js Backend Development Bootcamp 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Node.js Backend Development Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Async Iteration Over Streams

Node.js Readable streams emit chunks over time. The classic way to consume them is by wiring up 'data', 'end', and 'error' event listeners. That works, but it scatters your logic across callbacks and makes pausing, resuming, and error handling awkward.

Since Node 10, every Readable stream is an async iterable. That means you can consume it with a plain for await...of loop:

  • Sequential chunk processing, written top to bottom
  • Automatic backpressure — the loop pulls one chunk at a time
  • try/catch works for stream errors

This lesson shows how to consume, transform, and produce streams ergonomically with async iteration.

for-await-of Basics

A for await...of loop awaits each value produced by an async iterator before moving to the next. When applied to a Readable stream, each iteration yields one chunk (a Buffer by default, or a string if the stream encoding is set).

Here we read this script's own source file line-by-chunk. The loop body runs once per chunk and the loop naturally ends when the stream finishes:

import { createReadStream } from 'node:fs';
import { fileURLToPath } from 'node:url';

const self = fileURLToPath(import.meta.url);
const stream = createReadStream(self, { encoding: 'utf8' });

let chunks = 0;
let bytes = 0;
for await (const chunk of stream) {
  chunks++;
  bytes += chunk.length;
}
console.log('chunks:', chunks, 'chars:', bytes);

Built-in Backpressure

The biggest win of for await...of is automatic backpressure. The loop requests the next chunk only when the previous iteration's body has finished awaiting. If your body does slow async work (a DB write, an HTTP call), the stream is paused until you are ready.

You get this for free — no manual stream.pause() / stream.resume() dance. This simulated example processes each chunk with a delay; the stream waits between iterations:

import { Readable } from 'node:stream';
import { setTimeout as sleep } from 'node:timers/promises';

const source = Readable.from(['a', 'b', 'c', 'd']);

for await (const item of source) {
  // Slow consumer: stream is paused while we await
  await sleep(50);
  console.log('processed', item);
}
console.log('done');

Readable.from(): Iterable to Stream

Readable.from() turns any iterable or async iterable (arrays, generators, async generators) into a proper Readable stream. This is the bridge from plain data or generator logic into the stream ecosystem (pipes, HTTP responses, file writes).

You can hand it a generator function result to lazily produce values:

import { Readable } from 'node:stream';

function* counter(n) {
  for (let i = 1; i <= n; i++) {
    yield `line ${i}\n`;
  }
}

const stream = Readable.from(counter(3));

for await (const chunk of stream) {
  process.stdout.write(chunk);
}

Async Generators as Stream Sources

Readable.from() also accepts an async generator. This lets you produce stream data from asynchronous sources — paginated APIs, queued jobs, or timed events — while keeping clean sequential code.

The async generator below yields a record every short interval, and Readable.from() exposes it as a standard stream any Node consumer can read:

import { Readable } from 'node:stream';
import { setTimeout as sleep } from 'node:timers/promises';

async function* fetchPages() {
  for (let page = 1; page <= 3; page++) {
    await sleep(20); // simulate async fetch
    yield { page, items: page * 2 };
  }
}

const stream = Readable.from(fetchPages(), { objectMode: true });

for await (const record of stream) {
  console.log('got page', record.page, 'items', record.items);
}

Object Mode Streams

By default streams carry Buffer or string chunks. When you set objectMode: true, each chunk is an arbitrary JavaScript value — an object, a number, an array. This is essential when async-iterating over structured records rather than raw bytes.

Readable.from() auto-enables object mode when the source yields non-string/Buffer values, but being explicit keeps intent clear. Async iteration then yields your objects directly:

import { Readable } from 'node:stream';

const users = [
  { id: 1, name: 'Ada' },
  { id: 2, name: 'Linus' },
  { id: 3, name: 'Grace' },
];

const stream = Readable.from(users, { objectMode: true });

for await (const user of stream) {
  console.log(`#${user.id} -> ${user.name}`);
}

Transforming via Async Generators

You do not always need a Transform stream class to map or filter stream data. You can wrap the source stream in an async generator that itself uses for await...of, yielding transformed values. This composes naturally and reads like synchronous code.

Here a generator parses NDJSON-style lines into objects and filters them, all while preserving backpressure:

import { Readable } from 'node:stream';

async function* onlyActive(source) {
  for await (const record of source) {
    if (record.active) {
      yield record.name;
    }
  }
}

const input = Readable.from([
  { name: 'job-a', active: true },
  { name: 'job-b', active: false },
  { name: 'job-c', active: true },
], { objectMode: true });

for await (const name of onlyActive(input)) {
  console.log('active:', name);
}

Error Handling with try/catch

A major ergonomic gain: stream errors surface as thrown exceptions inside the loop, so a normal try/catch handles them. With event-based consumption you had to listen for 'error' separately and risk unhandled rejections.

If the stream emits 'error' mid-iteration, the awaited next() rejects and control jumps to catch:

import { Readable } from 'node:stream';

async function* faulty() {
  yield 1;
  yield 2;
  throw new Error('source blew up');
}

const stream = Readable.from(faulty(), { objectMode: true });

try {
  for await (const n of stream) {
    console.log('value', n);
  }
} catch (err) {
  console.error('caught:', err.message);
}

Early break Destroys the Stream

When you break, return, or throw out of a for await...of loop, Node calls the iterator's return() method, which destroys the underlying stream and releases its resources (file descriptors, sockets). This is a crucial cleanup guarantee.

So stopping after the first match does not leak the open file handle — the stream is torn down automatically:

import { Readable } from 'node:stream';

const stream = Readable.from(['alpha', 'beta', 'gamma', 'delta']);

for await (const word of stream) {
  console.log('checking', word);
  if (word === 'beta') {
    console.log('found it, stopping');
    break; // stream is destroyed on break
  }
}
console.log('destroyed?', stream.destroyed);

Consuming an HTTP Request Body

In a Node backend, an incoming HTTP request (req) is a Readable stream. Async iteration is a clean way to collect or process the request body without buffering everything manually.

This pattern accumulates chunks then parses JSON. Because it is server code (it binds a port and needs an HTTP client), it is illustrative rather than self-running here:

import { createServer } from 'node:http';

const server = createServer(async (req, res) => {
  try {
    const chunks = [];
    for await (const chunk of req) {
      chunks.push(chunk);
    }
    const body = Buffer.concat(chunks).toString('utf8');
    const data = JSON.parse(body || '{}');
    res.end(JSON.stringify({ received: data }));
  } catch (err) {
    res.statusCode = 400;
    res.end('bad request');
  }
});

server.listen(3000);

Iterator Helpers and stream.compose()

Modern Node adds higher-order helpers directly on Readable streams that consume the async iterator under the hood: stream.map(), stream.filter(), stream.toArray(), stream.take(), and more. They keep backpressure while letting you avoid hand-written loops.

You can chain them fluently. toArray() drains the stream and resolves to a plain array:

  • .map(fn) — transform each chunk (supports async fn + concurrency)
  • .filter(fn) — keep matching chunks
  • .toArray() — collect everything into an array
import { Readable } from 'node:stream';

const result = await Readable.from([1, 2, 3, 4, 5])
  .filter((n) => n % 2 === 1)
  .map((n) => n * 10)
  .toArray();

console.log(result); // [10, 30, 50]

Quick Check

You consume a Readable file stream with for await...of and break out of the loop after finding the first matching line. What happens to the underlying file resource?

Recap

You learned to consume and produce Node streams with async iteration:

  • for await...of reads a Readable one chunk at a time with built-in backpressure and try/catch error handling.
  • Readable.from() converts arrays, generators, and async generators into streams; use objectMode for structured records.
  • Wrap a source in an async generator to map/filter while preserving backpressure — no Transform class needed.
  • break/return/throw destroys the stream and frees resources automatically.
  • Helpers like .map(), .filter(), and .toArray() offer a fluent alternative to manual loops.

Reach for async iteration whenever you want sequential, readable, leak-safe stream consumption in your backend.

자주 묻는 질문

“스트림에서 비동기 반복자 및 for-await-of 사용하기” 강의는 무료인가요?

네 — “스트림에서 비동기 반복자 및 for-await-of 사용하기” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Node.js Backend Development Bootcamp 강의 전체를 잠금 해제할 수 있습니다. Node.js Backend Development Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.

“스트림에서 비동기 반복자 및 for-await-of 사용하기”에서 뭘 배우나요?

비동기 반복으로 읽기 가능 스트림을 편리하게 소비하고 스트림과 비동기 생성기 사이를 변환합니다. 브라우저에서 직접 실행하는 실습 코드로 Node.js Backend Development Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Node.js Backend Development Bootcamp을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Node.js Backend Development Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“스트림에서 비동기 반복자 및 for-await-of 사용하기” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Node.js Backend Development Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Node.js Backend Development Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 읽기 가능, 쓰기 가능, 양방향 및 변환 스트림 내부 구조
  2. _transform 및 _flush를 활용한 사용자 지정 변환 스트림 구현
  3. 백프레셔, pipe() 및 pipeline() 유틸리티
  4. 스트림에서 비동기 반복자 및 for-await-of 사용하기
← Node.js Backend Development Bootcamp(으)로 돌아가기