빠른 비교
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
import { createWriteStream } from "node:fs";
const response = await fetch("https://example.com/large-file.bin");
const nodeStream = Readable.fromWeb(response.body);
await pipeline(nodeStream, createWriteStream("output.bin"));스트림 구분
먼저 머릿속에 잡아 둘 기본형은 아래입니다.
fetch().body: WebReadableStream- 파일/소켓/zlib: Node streams
- Web -> Node:
Readable.fromWeb(...) - Node -> Web:
Readable.toWeb(...) pipeline()은 Node stream 기준
fetch 계열은 Web Streams 쪽이다
브라우저 표준에서 온 API라서 response.body는 Web ReadableStream입니다. Node 전통 스트림과 같은 메서드가 있다고 기대하면 안 됩니다.
파일, 소켓, pipeline()은 Node streams 쪽이다
기존 Node 런타임 자원과 자연스럽게 맞물리는 건 여전히 Node streams입니다.
변환 포인트만 기억하면 된다
두 모델을 모두 깊게 외우기보다, 경계에서 Readable.fromWeb()과 Readable.toWeb()를 떠올릴 수 있으면 실전에서는 충분한 경우가 많습니다.
중요한 건 API 이름보다 어느 세계의 스트림인지다
겉으로는 둘 다 "ReadableStream"처럼 보여도, 연결 가능한 메서드와 도구 체인이 다릅니다. 실전 실패도 대부분 문법보다 "지금 이 스트림이 Web 쪽인지 Node 쪽인지"를 놓쳐서 생깁니다.
경계가 잦은 곳은 fetch, Response, 파일 저장이다
원격 다운로드를 fetch()로 받고, 결과를 파일로 저장하거나 gzip 파이프라인에 태우는 순간 두 모델이 바로 만납니다.
그래서 추상 비교보다도 다운로드/업로드 경계에서 다시 찾는 기준으로 읽는 편이 더 실전적입니다.
import { Readable } from "node:stream";
const response = await fetch("https://example.com/data.json");
const nodeReadable = Readable.fromWeb(response.body);
for await (const chunk of nodeReadable) {
console.log(chunk.length);
}반대로 Node 쪽에서 만든 스트림을 Web API에 넘겨야 하면 Readable.toWeb()을 떠올리면 됩니다. 중요한 것은 "지금 어느 쪽 도구 체인을 쓰고 싶은가"를 먼저 정하는 것입니다.
언제 무엇을 쓸까
체크포인트
- 파일/소켓/zlib: Node streams
fetch().body: Web Streams- Web -> Node 변환:
Readable.fromWeb(...) - Node -> Web 변환:
Readable.toWeb(...) - 브라우저 호환 흐름: Web Streams
pipeline()이나pipe()를 쓰려면 Node stream인지 먼저 확인- 파일 저장, 압축,
pipeline()직전이 가장 흔한 변환 지점이다
주의할 점
response.body.pipe(...)가 안 되는 이유는 스트림이 틀려서다. fetch 응답을 Node 파이프라인에 넣고 싶으면 먼저 변환해야 한다.
참고 링크
2 sources