์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ๋ฐ์๋ก์, ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ก ๋คํธ์ํฌ๋ก๋ถํฐ ๋ฐ์ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ Chunk๋จ์๋ก ์ฝ๊ณ ๋ค๋ฃจ๋ ๊ฒ์ ๋งค์ฐ ์ ์ฉํฉ๋๋ค! ๊ทธ๋ฌ๋ ์ด๋ป๊ฒ ์คํธ๋ฆผ API์ Readable stream์ ์ ์ฌ์ฉํ ์ ์์๊น์. ์ด๋ฒ ๋ด์ฉ์ ๊ทธ๊ฒ์ ์ค๋ช ํ๊ณ ์์ต๋๋ค.
Note: This article assumes that you understand the use cases of readable streams, and are aware of the high-level concepts. If not, we suggest that you first read the Streams concepts and usage overview and dedicated Streams API concepts article, then come back.
Note: If you are looking for information on writable streams try Using writable streams instead.
Browser support
ํ์ด์ดํญ์ค 65+ ์ ํฌ๋กฌ 42+ ์์ Fetch Body ๊ฐ์ฒด๋ฅผ ์คํธ๋ฆผ์ผ๋ก์ ์ฌ์ฉ ํ ์ ์๊ณ , custom readable ์คํธ๋ฆผ์ ๋ง๋ค์ ์์ต๋๋ค. ํ์ฌ Pipe chains์ ๊ฒฝ์ฐ ์ค์ง ํฌ๋กฌ์์๋ง ์ง์ํ๊ณ ์๊ณ ๊ทธ ๊ธฐ๋ฅ์ ๋ณ๊ฒฝ๋ ์ ์์ต๋๋ค.
Finding some examples
์ด๋ฒ ๋ด์ฉ๊ณผ ๊ด๋ จํ ๋ง์ ์์ ๋ฅผ dom-examples/streams ์์ ์ดํด๋ณผ์ ์์ต๋๋ค. ์ด๊ณณ์์ ๋ชจ๋ ์์ค๋ฅผ ํ์ธํ ์ ์์ ๋ฟ๋ง ์๋๋ผ ์์ ์ฌ์ดํธ ๋งํฌ๋ ํ์ธํ ์ ์์ต๋๋ค.
Consuming a fetch as a stream
Fetch API ๋ ๋คํธ์ํฌ๋ฅผ ํตํด ๋ฆฌ์์ค๋ฅผ ๊ฐ์ ธ์ค๋ XHR์ ํ๋์ ์ธ ๋์์ฑ ์ ๋๋ค. Fetch API์ ์๋ง์ ์ด์ ๊ฐ์ด๋ฐ ๊ฐ์ฅ ํ๋ฅญํ์ ์ ์ต๊ทผ ๋ธ๋ผ์ฐ์ ๋ค์ด Fetch Response๋ฅผ Readable ์คํธ๋ฆผ์ผ๋ก ์ด์ฉํ ์ ์๊ฒ ํ๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ฒ ์ ๋๋ค.
Body
๋ฏน์ค์ธ์ body
์์ฑ์ ํฌํจํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด body
์์ฑ์ body
์ ๋ด์ฉ์ Readable ์คํธ๋ฆผ์ผ๋ก ๋
ธ์ถ์ํค๋ ๊ฐ๋จํ getter ์
๋๋ค. ์ด Body
๋ฏน์ค์ธ์ Request
์ Response
์ธํฐํ์ด์ค๋ก๋ถํฐ ๊ตฌํ ๋๋ฉฐ, ๋ฐ๋ผ์ ๋ ๊ฒฝ์ฐ ๋ชจ๋ ์ฌ์ฉ ํ ์ ์์ต๋๋ค. ๋ค๋ง Response body์ ์คํธ๋ฆผ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์กฐ๊ธ๋ ๋ช
ํํฉ๋๋ค.
์ฐ๋ฆฌ์ Simple stream pump์์์์ ๋ณด์ฌ์ฃผ๋ฏ(see it live also), Readable ์คํธ๋ฆผ์ ๋ ธ์ถ์ํค๊ธฐ ์ํด์๋ ๋จ์ง Response์ body ์์ฑ์ ์ ๊ทผํ๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค.
// ์ค๋ฆฌ์ง๋ ์ด๋ฏธ์ง๋ฅผ Fetch ํจ
fetch('./tortoise.png')
// body ๋ฅผ ReadableStream์ผ๋ก ๊ฐ๊ณตํจ
.then(response => response.body)
์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ReadableStream
๊ฐ์ฒด๋ฅผ ์ ๊ณตํด ์ค๋๋ค.
Attaching a reader
์ด์ ์ฐ๋ฆฌ๋ ์คํธ๋ฆผํ๋ body๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์ด ์คํธ๋ฆผ์ ์ฝ๊ธฐ ์ํด์๋ ๋ฆฌ๋๊ธฐ๋ฅผ ๋ถ์ฌ์ผ ํฉ๋๋ค. ์ด ์์
์ ReadableStream.getReader()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํฉ๋๋ค.
// ์ค๋ฆฌ์ง๋ ์ด๋ฏธ์ง๋ฅผ Fetch ํจ
fetch('./tortoise.png')
// body ๋ฅผ ReadableStream์ผ๋ก ๊ฐ๊ณตํจ
.then(response => response.body)
.then(body => {
const reader = body.getReader();
์ด ๋ฉ์๋๋ฅผ ์คํํ๋ฉด ๋ฆฌ๋๊ธฐ๊ฐ ์์ฑ๋๊ณ ์ด ๋ฆฌ๋๊ธฐ๋ฅผ ์คํธ๋ฆผ์ ๊ณ ์ (locks) ์ํต๋๋ค. ReadableStreamDefaultReader.releaseLock()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋๋ฑ ์ด ๊ณ ์ (locks)์ ํ๊ธฐ์ ๊น์ง๋(released), ๊ทธ ์ด๋ค ๋ค๋ฅธ ๋ฆฌ๋๊ธฐ๋ค๋ ์ด ์คํธ๋ฆผ์ ์ฝ์์ ์์ต๋๋ค.
๋ํ response.body
๋ ๋๊ธฐ์ด๋ฏ๋ก ๊ตณ์ด ํ๋ก๋ฏธ์ค๋ฅผ ์ฌ์ฉํ ํ์๊ฐ ์์ผ๋ฉฐ, ์์ ์์๋ฅผ ํ๋ฒ์ ์คํ
์ผ๋ก ์ค์ผ์ ์๋ค๋ ์ ์ ์ฃผ๋ชฉํด ์ฃผ์ญ์์
// ์ค๋ฆฌ์ง๋ ์ด๋ฏธ์ง๋ฅผ Fetch ํจ
fetch('./tortoise.png')
// body ๋ฅผ ReadableStream์ผ๋ก ๊ฐ๊ณตํจ
.then(response => {
const reader = response.body.getReader();
Reading the stream
์ด์ ์ฐ๋ฆฌ๋ ์ฐ๋ฆฌ์ ๋ฆฌ๋๊ธฐ๋ฅผ ์ถ๊ฐํ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ๋ฆฌ๋๊ธฐ์ ReadableStreamDefaultReader.read()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์คํธ๋ฆผ์ผ๋ก ๋ถํฐ data chunk๋ค์ ์ฝ์์ ์๊ฒ ๋์์ต๋๋ค.
์ ํํ๊ฒ๋ ์ด ๋ฉ์๋๋ ๊ฐ ์คํธ๋ฆผ์ผ๋ก๋ถํฐ ํ๋์ data chunk๋ฅผ ์ฝ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ data chunk๋ ์ฐ๋ฆฌ๊ฐ ์ํ๋๋๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ์ฐ๋ฆฌ์ Simple stream pump example์์๋ ReadableStreamDefaultReader.read()
์ฌ์ฉํ์ฌ Data Chunk๋ฅผ ์๋ก์ด ์ปค์คํ
ReadableStream
์ ์ง์ด ๋ฃ๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ง์ฝ ์ฝ์์ ์๋ ๋ค์ Data Chunk๊ฐ ์๋ค๋ฉด, ReadableStreamDefaultReader.read()
๋ฅผ ๋ค์ ์ฌ์ฉํ์ฌ ๋ค์ Data Chunk๋ฅผ ์ปค์คํ
ReadableStream
์ ์ง์ด ๋ฃ์ต๋๋ค. ๋์ด์ ์ฝ์์ ์๋ Data Chunk๊ฐ ์๋ค๋ฉด, ๊ทธ ์ปค์คํ
ReadableStream
(์ฐ๋ฆฌ๋ ์ด ์๋ก์ด Readable ์คํธ๋ฆผ์ ๋ํด ๋ค์ ์น์
์์ ๋ค ์์ธํ ์ดํด ๋ณผ๊ฒ ์
๋๋ค.)์ ํตํด ์๋ก์ด Response
๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค. ๊ทธ ๋ค์ ์ด Response
๊ฐ์ฒด๋ฅผ Blob
ํํ๋ก ๋ณํํ๊ณ ์ด Blob
์ผ๋ก ๋ถํฐ URL.createObjectURL()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ URL ๊ฐ์ฒด๋ฅผ ์์ฑํฉ๋๋ค. ๋ง์ง๋ง์ผ๋ก ์ด URL๊ฐ์ฒด๋ฅผ {htmlelement("img")}} ์ ์ ์ฉํ์ฌ ์ด๋ฏธ์ง๋ฅผ ๋ณด์ฌ์ค์ผ๋ก์ fetch๋ ์ค๋ฆฌ์ง๋ ์ด๋ฏธ์ง๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๋ณต์ฌํ๋ ๊ฒ์
๋๋ค.
return new ReadableStream({
start(controller) {
return pump();
function pump() {
// ์คํธ๋ฆผ์ ๋ค์ Chunk์ ๋ํ ์ก์ธ์ค๋ฅผ ์ ๊ณตํ๋ psomise๋ฅผ ๋ฆฌํดํ๋ค.
return reader.read().then(({ done, value }) => {
// ๋์ด์ ์ฝ์ ๋ฐ์ดํฐ ์กฐ๊ฐ์ด ์์๋ ์คํธ๋ฆผ์ ๋ซ๋๋ค
if (done) {
controller.close();
return;
}
// ๋ฐ์ดํฐ ์กฐ๊ฐ์ ์๋ก์ด ์คํธ๋ฆผ(์๋ก ๋ง๋๋ ์ปค์คํ
์คํธ๋ฆผ)์ ๋ฃ๋๋ค.
controller.enqueue(value);
return pump();
});
}
}
})
})
.then(stream => new Response(stream))
.then(response => response.blob())
.then(blob => URL.createObjectURL(blob))
.then(url => console.log(image.src = url))
.catch(err => console.error(err));
์ด๋ป๊ฒ read()
๊ฐ ์ฌ์ฉ๋์๋์ง ์์ธํ ๋ค์ฌ๋ค ๋ด
์๋ค. ์ ์์ ์ pump()
ํจ์๋ ์ ์ผ๋จผ์ read()
๋ฅผ ์คํํ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด read()
๋ ์คํธ๋ฆผ์ผ๋ก๋ถํฐ ์ฝ์ด ๋ค์ธ ๋ด์ฉ์ ๊ฒฐ๊ณผ๋ฅผ { done, value }
์ ํ์์ผ๋ก ๊ฐ์ง๊ณ ์๋ pomise๋ฅผ ๋ฐํํฉ๋๋ค.
return reader.read().then(({ done, value }) => {
์คํธ๋ฆผ์ผ๋ก๋ถํฐ ์ฝ์ด ๋ค์ธ ๋ด์ฉ์ ์๋ 3๊ฐ์ง ํ์ ์ด ์์ต๋๋ค.
- chunk๋ฅผ ์์ง ์ฝ์์ ์๋ ๊ฒฝ์ฐ์ ํ๋ก๋ฏธ์ค๋
{ value: theChunk, done: false }
๊ฐ์ฒด์ ํจ๊ป fulfill ๋ฉ๋๋ค. - ์คํธ๋ฆผ์ด ๋ซํ ๊ฒฝ์ฐ์ ํ๋ก๋ฏธ์ค๋
value: undefined, done: true }
๊ฐ์ฒด์ ํจ๊ป fulfill ๋ฉ๋๋ค. - ์คํธ๋ฆผ์์ ์ ๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ promise๋ ๊ด๋ จ ์ ๋ฌ์ ํจ๊ป reject ๋ฉ๋๋ค.
๋ค์์ผ๋ก ์ฐ๋ฆฌ๋ done
์ด true
์ธ์ง ์๋์ง ํ์ธํด ๋ด์ผ ํฉ๋๋ค.
๋ง์ฝ done
์ด true
๋ผ๋ฉด ๋์ด์ ์ฝ์ด๋ค์ผ chunk๊ฐ ์๋ค๋ ๋ป์
๋๋ค. ๋ฐ๋ผ์ ์ฐ๋ฆฌ๋ ํจ์ ๋ฐ์ผ๋ก ๋น ์ ธ ๋๊ฐ์ผ ํ๊ณ ReadableStreamDefaultController.close()
๋ฅผ ํตํ์ฌ ์ปค์คํ
์คํธ๋ฆผ์ ๋ซ์์ผ ํฉ๋๋ค.
if (done) {
controller.close();
return;
}
Note: ์ฌ๊ธฐ์ ์ฌ์ฉํ close()
๋ ์๋ก๋ง๋ ์ปค์คํ
์คํธ๋ฆผ์ ์ผ๋ถ์ด๋ฉฐ ์ค๋ฆฌ์ง๋ ์คํธ๋ฆผ์ ๊ฒ์ด ์๋๋๋ค. ์ปค์คํ
์คํธ๋ฆผ์ ๋ํด์๋ ๋ค์์น์
์์ ๋ ์์ธํ ์ดํด ๋ณผ ์์ ์
๋๋ค.
๋ง์ฝ done
์ด true
๊ฐ ์๋๋ผ๋ฉด, ์ฐ์ ์ฝ์ด ๋๋ฆฐ Chunk๋ฅผ ์ฒ๋ฆฌํ๊ณ (value
์์ฑ), pump()
ํจ์๋ฅผ ์ฌ๊ท์ ์ผ๋ก ๋ค์ ํธ์ถ ํจ์ผ๋ก์ ๋ค์ chunk๋ฅผ ์ฝ์ด ๋๋ฆฝ๋๋ค.
// ๋ค์ data chunk๋ฅผ ์๋ก์ด readable ์คํธ๋ฆผ์ ์ง์ด ๋ฃ์
controller.enqueue(value);
return pump();
๋ค์์ ์คํธ๋ฆผ ๋ฆฌ๋๊ธฐ๋ฅผ ์ฌ์ฉํ ๋์ ๊ธฐ๋ณธ์ ์ธ ํจํด ์ ๋๋ค.
- ์คํธ๋ฆผ์ ์ฝ์์ผ๋ก์ ์คํ๋๋ ํจ์๋ฅผ ์์ฑํฉ๋๋ค.
- ๋ง์ฝ ์ฝ์์ ์๋ ์คํธ๋ฆผ์ด ๋์ด์ ์๋ค๋ฉด, ํจ์๋ฅผ ๋ฆฌํด ์ํต๋๋ค.
- ๋ง์ฝ ์ฝ์์ ์๋ ์คํธ๋ฆผ์ด ์์ง ๋จ์ ์๋ค๋ฉด, ์ฐ์ ์ฝ์ด ๋๋ฆฐ chunk๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๋ค์ chunk๋ฅผ ์ฝ์ด ๋๋ฆฌ๊ธฐ ์ํด ํจ์๋ฅผ ๋ค์ ์คํํฉ๋๋ค.
- ๋์ด์ ์ฝ์์ ์๋ ์คํธ๋ฆผ์ด ์์๋๊น์ง ํจ์๋ฅผ ์ฌ๊ท์ ์ผ๋ก ์คํํ๊ณ , ์ต์ข ์ ์ผ๋ก ์ฝ์์ ์๋ ์คํธ๋ฆผ์ด ์์๊ฒฝ์ฐ 2๋ฒ Step์ ๋ฐ๋ฆ ๋๋ค.
Creating your own custom readable stream
๋๋ฒ์งธ ํํธ์์ ์ฌ์ฉํ๋ Simple stream pump example ์์ ์์ ์ฐ๋ฆฌ๋ fetch body๋ก ๋ถํฐ ์ฝ์ด๋๋ฆฐ ์ด๋ฏธ์ง์ ๋ํ data๋ฅผ ์ฐ๋ฆฌ๊ฐ ์์ฒด์ ์ผ๋ก ๋ง๋ ์ปค์คํ
readable ์คํธ๋ฆผ์ ๋ค์ ์ฎ๊ฒจ ์ฌ์์ต๋๋ค. ๊ทธ๋ผ ์ด๋ป๊ฒ ์คํธ๋ฆผ์์์ฒด์ ์ผ๋ก ๋ง๋ค์ ์์๊น์? ์ฐ๋ฆฌ๋ ReadableStream()
์์ฑ์ ํจ์๋ฅผ ํตํด ์ปค์คํ
readable ์คํธ๋ฆผ์ ๋ง๋ค ์ ์์ต๋๋ค.
The ReadableStream() constructor
Fetch์ ๊ฐ์ด ๋ธ๋ผ์ฐ์ ์์ ์คํธ๋ฆผ์ ์ ๊ณตํ ๋ ๊ทธ๊ฒ์ ์ฝ์ด ๋ค์ด๋ ์ผ์ ์ฌ์ด ์ผ์
๋๋ค. ๊ทธ๋ฌ๋ ๋๋๋ก ์๋ก์ด ์ปค์คํ
์คํธ๋ฆผ์ ๋ง๋ค๊ณ ์ด๊ฒ์ data chunk๋ค๋ก ์ฑ์๋ฃ์ด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ReadableStream()
์ ์๋์ ๊ตฌ๋ฌธ๊ณผ ๊ฐ์ด ์ฌ์ฉํจ์ผ๋ก์ ์ด๊ฒ์ ๊ฐ๋ฅํ๊ฒ ํ ์ ์์ต๋๋ค. ๊ตฌ๋ฌธ์ด ์ฒ์์๋ ๋ค์ ๋ณต์กํด ๋ณด์ผ์ ์์ต๋๋ค๋ง, ์ค์ ๋ก๋ ๊ทธ๋ ๊ฒ ๋ณต์กํ์ง ์์ต๋๋ค.
๊ธฐ๋ณธ์ ์ธ ํต์ฌ ๊ตฌ๋ฌธ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
const stream = new ReadableStream({
start(controller) {
},
pull(controller) {
},
cancel() {
},
type,
autoAllocateChunkSize
}, {
highWaterMark,
size()
});
์์ฑ์ ํจ์๋ ๋๊ฐ์ ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ๋ฐ์ต๋๋ค. ์ฒซ๋ฒ์งธ ์ธ์๋ ํ์ ๊ฐ์ด๋ฉฐ, ์ด๊ฒ์ ์ฐ๋ฆฌ๊ฐ ์ฝ์ด ๋ค์ผ ๊ธฐ๋ณธ ์์ค์ ๋ชจ๋ธ์ JavasScrip ์ ์ผ๋ก ์์ฑ ํฉ๋๋ค. ๋๋ฒ์งธ ์ธ์๋ ์ต์ ๊ฐ์ด๋ฉฐ, ์ด๊ฒ์ ์ปค์คํ ์คํธ๋ฆผ์ ์ฌ์ฉํ ์ปค์คํ queuing ์ ๋ต์ ์๋ฆฝํ๊ฒ ํฉ๋๋ค. ๋๋ฒ์งธ ์ธ์์ ๊ฒฝ์ฐ ๋งค์ฐ ๋๋ฌผ๊ฒ ์ค์ ํ๋ ๊ฐ์ด๋ฏ๋ก ์ง๊ธ์ ์ฒซ๋ฒ์งธ ์ธ์์ ์ง์คํ๋๋ก ํ๊ฒ ์ต๋๋ค.
์ฒซ๋ฒ์งธ ์ธ์์ธ ๊ฐ์ฒด๋ 5๊ฐ์ ๋งด๋ฒ๋ฅผ ๊ฐ์ง์ ์์ผ๋ฉฐ, ์ ์ผ ์ฒซ๋ฒ์งธ ๋งด๋ฒ๋ง ํ์ ์ ๋๋ค.
start(controller)
โReadableStream
์ด ์์ฑ๋์ ๋ง์ ๋ฑ ํ๋ฒ๋ง ํธ์ถ ๋๋ ๋ฉ์๋ ์ ๋๋ค. ์ด ๋ฉ์๋์๋ ์คํธ๋ฆผ์ ๊ธฐ๋ฅ์ ์ค์ ํ ์ ์๋ ์ฝ๋๊ฐ ํฌํจ๋์ด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ฐ์ดํฐ ์์ฑ์ ์์ํ๋ค๊ฑฐ๋ ์๋๋ฉด ์์ค์ ์ ๊ทผํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ฝ๋๋ฑ์ด ๋ค์ด๊ฐ๊ฒ ๋ ๊ฒ์ ๋๋ค.pull(controller)
โ ์ด ๋ฉ์๋๋ ์คํธ๋ฆผ ๋ด๋ถ์ queue๊ฐ ๊ฐ๋ ์ฐฐ๋๊น์ง ๋ฐ๋ณต์ ์ผ๋ก ํธ์ถ ๋ฉ๋๋ค. ๋ ๋ง์ ์ฒญํฌ๊ฐ ํ์ ๋ค์ด๊ฐ ๋ ์คํธ๋ฆผ์ ์ ์ดํ๋ โโ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค.cancel()
โ ์ด ๋ฉ์๋๋ ์คํธ๋ฆผ์ด ์บ์ฌ๋ ๋ ํธ์ถ ๋ฉ๋๋ค (์๋ฅผ ๋ค์ด {domxref("ReadableStream.cancel()")}} ์ด ํธ์ถ ๋์์๋). ๋ฉ์๋์ ๋ด์ฉ์ ์คํธ๋ฆผ ์์ค์ ๋ํ ์ก์ธ์ค๋ฅผ ํด์ ํ๋ ๋ฐ ํ์ํ ๋ชจ๋ ๊ฒ์ ์ํํด์ผํฉ๋๋ค.type
andautoAllocateChunkSize
โ ์คํธ๋ฆผ์ด ๋ฐ์ดํธ ์คํธ๋ฆผ์์ ๋ํ ๋ด๊ธฐ ์ํด ์ฌ์ฉ๋ฉ๋๋ค. ๋ฐ์ดํธ ์คํธ๋ฆผ์ ๋ชฉ์ ๊ณผ ์ฌ์ฉ ์ฌ๋ก๊ฐ ์ผ๋ฐ (๊ธฐ๋ณธ) ์คํธ๋ฆผ๊ณผ ์ฝ๊ฐ ๋ค๋ฅด๋ฏ๋ก ํฅํ ์์ต์์์ ๋ณ๋๋ก ๋ค๋ฃฐ ๊ฒ์ ๋๋ค. ๋ํ ์์ง ์ด๋ ๊ณณ์์๋ ๊ตฌํ๋์ง ์์์ต๋๋ค.
simple example code ๋ฅผ ๋ค์ํ๋ฒ ์ดํด๋ณด๋ฉด, ReadableStream()
์์ฑ์๊ฐ start()
๋ฉ์๋ ๋จ ํ๋๋ง ๊ฐ์ง๊ณ ์๋ค๋ ๊ฒ์ ์์์ฑ์ ์์ ๊ฒ ์
๋๋ค. ์ด start()
๋ฉ์๋ fetch๋ ์คํธ๋ฆผ์ผ๋ก ๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด ๋ค์ด๊ณ ์์ต๋๋ค.
return new ReadableStream({
start(controller) {
return pump();
function pump() {
return reader.read().then(({ done, value }) => {
// ๋์ด์ ์ฝ์์ ์๋ data๊ฐ ์๋ค๋ฉด ์คํธ๋ฆผ์ ๋ซ๋๋ค
if (done) {
controller.close();
return;
}
// ๋ฐ์ดํฐ ์กฐ๊ฐ์ ์๋ก์ด ์คํธ๋ฆผ(์๋ก ๋ง๋๋ ์ปค์คํ
์คํธ๋ฆผ)์ ๋ฃ๋๋ค.
controller.enqueue(value);
return pump();
});
}
}
})
})
ReadableStream controllers
ReadableStrem() ์์ฑ์์ ์ธ์๋ก ์ ๋ฌ๋ ๊ฐ์ฒด์์ start()
์ pull()
๋ฉ์๋์ controller๋ผ๋ ์ธ์๊ฐ ์ ๋ฌ๋๋ ๊ฒ์ ๋ณผ์ ์์ต๋๋ค. ์ด๊ฒ์ ReadableStreamDefaultController
ํด๋์ค์ ์ธ์คํด์ค์ด๋ฉฐ ์ฐ๋ฆฌ์ ์คํธ๋ฆผ์ ์ ์ดํ๋๋ฐ ์ฌ์ฉ ๋ฉ๋๋ค.
์ฐ๋ฆฌ์ ์์ ์์, ์ฐ๋ฆฌ๋ fetch๋ body๋ก๋ถํฐ chunk์ ๊ฐ์ ์ฝ์ ๋ค ๊ทธ ๊ฐ์ ์ปค์คํ
์คํธ๋ฆผ์ ์ง์ด ๋ฃ๊ธฐ ์ํด Controller์ enqueue()
๋ฉ์๋๋ฅผ ์์ฉํ๊ณ ์์ต๋๋ค.
๋ํ, fetch๋ body๋ฅผ ์ฝ์ด ๋ค์ด๋ ๊ฒ์ด ๋๋๋ฉด ์ปจํธ๋กค๋ฌ์ close()
๋ฅผ ์ฌ์ฉํ์ฌ ์ปค์คํ
์คํธ๋ฆผ์ ๋ซ์ต๋๋ค. ์ด๋ ์ด๋ฏธ ์ฝ์
๋ chunk๋ค์ ์ฌ์ ํ ์ฝ์์ ์์ง๋ง ์๋ก์ด chunk๋ ์ง์ด ๋ฃ์์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝ๋ ๊ฒ์ด ๋ชจ๋ ๊ธ๋๋ฉด ์คํธ๋ฆผ์ ๋ซํ๋๋ค.
Reading from custom streams
์ฐ๋ฆฌ์ Simple stream pump example ์์, ์ฐ๋ฆฌ๋ {domxref("Response.Response", "Response")}} ์์ฑ์ ํจ์์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ปค์คํ
readable ์คํธ๋ฆผ์ ์ธ์๋ก ์ ๋ฌํ์์ผ๋ฉฐ ๊ทธ๋ ๊ฒ ์์ฑ๋ response ์ธ์คํด์ค๋ฅผ blob()
์ผ๋ก ์ฌ์ฉ ํ์์ต๋๋ค.
.then(stream => new Response(stream))
.then(response => response.blob())
.then(blob => URL.createObjectURL(blob))
.then(url => console.log(image.src = url))
.catch(err => console.error(err));
๊ทธ๋ฌ๋ ์ปค์คํ
์คํธ๋ฆผ์ ์ฌ์ ํ ReadableStream
์ธ์คํด์ค์
๋๋ค. ์ฆ, ์ฐ๋ฆฌ๋ ์ฌ์ ํ ๋ฆฌ๋๊ธฐ๋ฅผ ์ปค์คํ
์คํธ๋ฆผ์ ๋ถ์ผ์ ์๋ค๋ ๋ป์
๋๋ค. ์๋ฅผ ๋ค์ด Simple random stream demo (see it live also) ๋ฅผ ์ดํด ๋ณด์ญ์์. ์ด ์์ ์์๋ ์ปค์คํ
์คํธ๋ฆผ์ ์์ฑํํ, ๋๋ค ๋ฌธ์์ด์ ์์ฑ๋ ์ปค์คํ
์คํธ๋ฆผ์ ์ง์ด ๋ฃ์ต๋๋ค. ๊ทธ ํ ๋ฌธ์์ด ์์ฑ ์ค์ง ๋ฒํผ์ ๋๋ ์๋ ์ปค์คํ
์คํธ๋ฆผ์ ์ง์ด ๋ฃ์๋ ๋๋ค ๋ฌธ์์ด์ ์ปค์คํ
์คํธ๋ฆผ์ผ๋ก ๋ถํฐ ๋ค์ ์ฝ์ด ์ต๋๋ค.
Note: FetchEvent.respondWith()
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์คํธ๋ฆผ์ ๋ค๋ฃจ๊ธฐ ์ํด์๋ ์คํธ๋ฆผ์ ์ฝ์
๋ ๋ด์ฉ์ด ๋ง๋์ Uint8Array
์ด์ด์ผ ํฉ๋๋ค. (TextEncoder
๋ฑ์ ์ฌ์ฉ)
Simple random stream demo (see it live also) ์์ ์ปค์คํ
์คํธ๋ฆผ ์์ฑ์ ํจ์๋ start()
๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ผ๋ฉฐ, ์ด ๋ฉ์๋๋ WindowTimers.setInterval()
์ ์ฌ์ฉํ์ฌ ๋งค์ด๋ง๋ค ์๋ก์ด ๋๋ค ๋ฌธ์์ด์ ์์ฑํ๊ณ ์ด ๋ฌธ์์ด์ ReadableStreamDefaultController.enqueue()
๋ฅผ ์ฌ์ฉํ์ฌ ์คํธ๋ฆผ์์ ๋ฃ์ต๋๋ค. ๋ง์ฝ ๋ฌธ์์ด ์์ฑ ์ค์ง ๋ฒํผ์ ๋๋ฅธ๋ค๋ฉด ์ด interval ์ด ์ทจ์๋จ๊ณผ ๋์์ readStream()
ํจ์๋ฅผ ํธ์ถํ์ฌ ์คํธ๋ฆผ์ผ๋ก ๋ถํฐ ์คํธ๋ฆผ์ ๋ฃ์ ๋ฌธ์์ด ์ ๋ถ๋ฅผ ์ฝ์ด ๋๋ฆฝ๋๋ค. ์ด๋ ์คํธ๋ฆผ์ chunk data(์ฌ๊ธฐ์๋ ๋๋ค ๋ฌธ์์ด)๋ฅผ ๋ฃ๋ ๊ฒ๋ ์ค๋จ ํ๊ธฐ๋๋ฌธ์ ์คํธ๋ฆผ์ ๋ซ์ต๋๋ค.
const stream = new ReadableStream({
start(controller) {
interval = setInterval(() => {
let string = randomChars();
// Add the string to the stream
controller.enqueue(string);
// show it on the screen
let listItem = document.createElement('li');
listItem.textContent = string;
list1.appendChild(listItem);
}, 1000);
button.addEventListener('click', function() {
clearInterval(interval);
readStream();
controller.close();
})
},
pull(controller) {
// We don't really need a pull in this example
},
cancel() {
// This is called if the reader cancels,
// so we should stop generating strings
clearInterval(interval);
}
});
์คํธ๋ฆผ์ ๋ฃ์ ๋ฌธ์์ด ์ ๋ถ๋ฅผ ์ฝ๊ณ ๋ค๋ฃจ๊ธฐ ์ํด ๋ง๋ ์ปค์คํ
ํจ์์ธ readStream()
ํจ์๋ฅผ ์ดํด๋ณด๋ฉด, ์ฐ์ ReadableStream.getReader()
๋ฅผ ์ฌ์ฉํ์ฌ ๋ฆฌ๋๊ธฐ๋ฅผ ์คํธ๋ฆผ์ ๊ณ ์ ์ํค๋ ๊ฒ์ ๋ณผ์ ์์ต๋๋ค. ๊ทธ ํ ์์ ์ดํด๋ดค๋ ํจํด๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก read()
๋ฅผ ์ฌ์ฉํ์ฌ chunk๋ฅผ ์ฝ์ด ๋ค์ด๊ณ done
์ด true
์ธ์ง ์๋์ง ํ์ธ ํฉ๋๋ค. ๋ง์ฝ true
์ด๋ฉด readStream()
ํจ์์ ํ๋ก์ธ์ค๋ฅผ ๋๋ด๋ฒ๋ฆฌ๊ณ ๊ทธ๋ ์ง ์์ผ๋ฉด ์ฝ์ด๋๋ฆฐ chunk๋ฅผ ํ์์ฒ๋ฆฌํ ํ read()
๋ฅผ ์ฌ๊ท์ ์ผ๋ก ์คํํฉ๋๋ค.
function readStream() {
const reader = stream.getReader();
let charsReceived = 0;
// read() returns a promise that resolves
// when a value has been received
reader.read().then(function processText({ done, value }) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (done) {
console.log("Stream complete");
para.textContent = result;
return;
}
charsReceived += value.length;
const chunk = value;
let listItem = document.createElement('li');
listItem.textContent = 'Read ' + charsReceived + ' characters so far. Current chunk = ' + chunk;
list2.appendChild(listItem);
result += chunk;
// Read some more, and call this function again
return reader.read().then(processText);
});
}
Closing and cancelling streams
์ฐ๋ฆฌ๋ ์ด๋ฏธ ์์ ์คํธ๋ฆผ์ ๋ซ๋ ๋ฉ์๋์ธ ReadableStreamDefaultController.close()
๋ฅผ ์ดํด๋ณด์์ต๋๋ค. ์ด๋ฏธ ์ธ๊ธํ๋ค ์ํผ, ์คํธ๋ฆผ์ด ๋ซํ๋ค ํ๋๋ผ๊ณ ์ด๋ฏธ ๋ค์ด๊ฐ ์๋ chunk๋ ์ฝ์ ์ ์์ต๋๋ค.
๋ง์ฝ ์คํธ๋ฆผ์ ์๋ฒฝํ๊ฒ ์ ๊ฑฐํ๊ณ ์ฝ์
๋ ๋ชจ๋ chunk๋ฅผ ๋ ๋ฆฌ๊ณ ์ถ๋ค๋ฉด, ReadableStream.cancel()
๋๋ ReadableStreamDefaultReader.cancel()
๋ฉ์๋๋ฅผ ์ฌ์ฉ ํ๋ฉด ๋ฉ๋๋ค.
Teeing a stream
๋๋ก๋ ํ๋์ ์คํธ๋ฆผ์ ๋์ ๋๋ฒ ์ฝ์ด๋ค์ฌ์ผ ํ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ReadableStream.tee()
๋ฉ์๋๊ฐ ์ด๋ฅผ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ReadableStream.tee()
๋ฉ์๋๋ ๋๊ฐ์ ๋
๋ฆฝ์ ์ธ ์นดํผ๋ ์คํธ๋ฆผ์ ๊ฐ์ง๊ณ ์๋ ๋ฐฐ์ด์ ์ ๊ณตํฉ๋๋ค. ์ด ์นดํผ๋ ๋๊ฐ์ ์คํธ๋ฆผ์ ๋๊ฐ์ ๋
๋ฆฝ์ ์ธ ๋ฆฌ๋๊ธฐ๋ก ๊ฐ๊ฐ ์ฝ์ด ๋ค์ผ ์ ์์ต๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ๋ ์๋ง ServiceWorker ์์์ ํ์ํ ๊ฒ์ ๋๋ค. ๋ง์ฝ ์๋ฒ๋ก๋ถํฐ fetch๋ response๋ฅผ ๋ธ๋ผ์ฐ์ ์๋ ์ ๋ฌํ๊ณ ์๋น์ค ์์ปค ์บ์์๋ ์ ๋ฌํด์ผ ํ๋ค๋ฉด ํ๋์ ์คํธ๋ฆผ์ ๋ํด ๋๊ฐ์ ์นดํผ๋ณธ์ด ํ์ ํ ๊ฒ์ ๋๋ค. ์๋ํ๋ฉด response body (Readablestream)๋ ๋จ ํ๋ฒ๋ง ์ฌ์ฉ๋ ์ ์๊ณ ํ๋์ Readablestream์ ํ๋์ ๋ฆฌ๋๊ธฐ๋ง ๋ถ์์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ ๋ด์ฉ์ ๋ํ ์์ ๋ฅผ Simple tee example (see it live also)์์ ์ดํด ๋ณผ์ ์์ต๋๋ค. ์ด ์์ ๋ ๋๋ค ๋ฌธ์์ด ์์ฑ ๋ฒํผ์ ๋ํ ์ด๋ฒคํธ๊ฐ ์๋ค๋ ์ ๊ณผ, ์ด ์์ ์์์ ์คํธ๋ฆผ์ teed๋์ด ๋๊ฐ์ ์คํธ๋ฆผ์ด ๋๊ฐ์ ๋ฆฌ๋๊ธฐ๋ก ์ฝ์ด์ง๋ค๋ ์ ๋ง ์ ์ธํ๋ฉด ์์ ์ดํด๋ณธ Simple random stream ์์ ์ ๋งค์ฐ ์ ์ฌํ๊ฒ ๋์ํฉ๋๋ค.
function teeStream() {
const teedOff = stream.tee();
readStream(teedOff[0], list2);
readStream(teedOff[1], list3);
}
Pipe chains
One very experimental feature of streams is the ability to pipe streams into one another (called a pipe chain). This involves two methods โ ReadableStream.pipeThrough()
, which pipes a readable stream through a writer/reader pair to transform one data format into another, and ReadableStream.pipeTo()
, which pipes a readable stream to a writer acting as an end point for the pipe chain.
This functionality is at a very experimental stage and is subject to change, so we have no explored it too deeply as of yet.
We have created an example called Unpack Chunks of a PNG (see it live also) that fetches an image as a stream, then pipes it through to a custom PNG transform stream that retrieves PNG chunks out of a binary data stream.
// Fetch the original image
fetch('png-logo.png')
// Retrieve its body as ReadableStream
.then(response => response.body)
// Create a gray-scaled PNG stream out of the original
.then(rs => logReadableStream('Fetch Response Stream', rs))
.then(body => body.pipeThrough(new PNGTransformStream()))
.then(rs => logReadableStream('PNG Chunk Stream', rs))
Summary
That explains the basics of โdefaultโ readable streams. Weโll explain bytestreams in a separate future article, once they are available in browsers.