Streams - 최종 가이드

Streams API로 스트림을 읽고 쓸 수 있고 변환하여 사용하는 방법을 알아보세요.

Streams API를 사용하면 네트워크를 통해 수신된 데이터 스트림에 프로그래밍 방식으로 액세스할 수 있습니다. 어떤 수단을 사용하든 로컬 시스템에 의해 JavaScript로 처리할 수 있습니다. 스트리밍에는 수신, 전송 또는 변환하려는 리소스를 분석하는 작업이 포함됩니다. 작은 청크로 나눈 다음 조금씩 처리합니다. 스트리밍은 브라우저는 웹페이지에 표시될 HTML 또는 동영상과 같은 애셋을 수신하더라도 기능은 2015년에 스트림과 함께 fetch가 도입되기 전에는 JavaScript에서 사용할 수 없었습니다.

이전에는 일종의 리소스 (동영상 또는 텍스트 파일 등)를 처리하려면 전체 파일을 다운로드하고 적절한 형식으로 역직렬화될 때까지 기다려야 합니다. 처리합니다 스트림을 사용할 수 있는 JavaScript에서는 이 모든 것이 변경됩니다. 이제 자바스크립트로 원시 데이터를 클라이언트에서 사용할 수 있게 되면 버퍼, 문자열 또는 blob을 생성하지 않아도 됩니다. 이를 통해 다양한 사용 사례를 활용할 수 있으며, 그중 일부는 아래에 열거되어 있습니다.

  • 동영상 효과: 효과를 적용하는 변환 스트림을 통해 읽을 수 있는 동영상 스트림을 파이핑합니다. 있습니다.
  • 데이터 (압축 해제): 선택적으로 변환 스트림을 통해 파일 스트림을 파이핑합니다. (de)를 압축 해제합니다.
  • 이미지 디코딩: 바이트를 디코딩하는 변환 스트림을 통해 HTTP 응답 스트림을 파이핑 비트맵 데이터로 변환한 다음 비트맵을 PNG로 변환하는 또 다른 변환 스트림을 통해 변환합니다. 만약 서비스 워커의 fetch 핸들러 내에 설치되므로 투명하게 폴리필할 수 있습니다. 새로운 이미지 형식인 AVIF와 같은 새로운 이미지 형식을 지원합니다.

브라우저 지원

ReadableStream 및 WritableStream

브라우저 지원

  • Chrome: 43. <ph type="x-smartling-placeholder">
  • Edge: 14. <ph type="x-smartling-placeholder">
  • Firefox: 65 <ph type="x-smartling-placeholder">
  • Safari: 10.1. <ph type="x-smartling-placeholder">

소스

TransformStream

브라우저 지원

  • Chrome: 67 <ph type="x-smartling-placeholder">
  • Edge: 79. <ph type="x-smartling-placeholder">
  • Firefox: 102 <ph type="x-smartling-placeholder">
  • Safari 14.1. <ph type="x-smartling-placeholder">

소스

핵심 개념

스트림의 다양한 유형을 자세히 살펴보기 전에 몇 가지 핵심 개념을 소개해 드리겠습니다.

덩어리

청크는 스트림에 쓰거나 스트림에서 읽는 단일 데이터입니다. 다음 값 중 하나일 수 있습니다. type; 서로 다른 유형의 청크를 포함할 수도 있습니다. 대부분의 경우 청크가 가장 원자적인 것은 아닙니다. 데이터의 단위입니다. 예를 들어, 바이트 스트림에 16으로 구성된 청크가 포함될 수 있습니다. 단일 바이트가 아닌 KiB Uint8Array 단위입니다.

읽을 수 있는 스트림

읽을 수 있는 스트림은 읽을 수 있는 데이터 소스를 나타냅니다. 즉, 데이터는 출력됩니다. 구체적으로 읽을 수 있는 스트림은 ReadableStream의 인스턴스입니다. 클래스에 대해 자세히 알아보세요.

쓰기 가능한 스트림

쓰기 가능한 스트림은 쓸 수 있는 데이터의 대상을 나타냅니다. 즉, 데이터가 쓰기 가능한 스트림에 들어옵니다. 구체적으로 쓰기 가능한 스트림은 WritableStream 클래스.

스트림 변환

변환 스트림은 스트림 쌍으로 구성됩니다. 쓰기 가능한 스트림은 쓰기 가능한 측이라고 합니다. 그리고 읽기 가능한 쪽으로 알려진 읽기 가능한 스트림이 있습니다. 이에 대한 실제 은유는 동시 인터프리터 즉시 다른 언어로 번역할 수 있습니다 변환 스트림에 특정 방식으로 읽기에 사용할 수 있는 새로운 데이터가 있습니다. 구체적으로 writable 속성과 readable 속성이 있는 모든 객체를 제공할 수 있습니다. 사용할 수 있습니다 그러나 표준 TransformStream 클래스를 사용하면 더 쉽게 제대로 얽혀 있는 한 쌍의 커버입니다.

파이프 체인

스트림은 주로 서로 파이핑하는 데 사용됩니다. 읽을 수 있는 스트림은 읽을 수 있는 스트림의 pipeTo() 메서드를 사용하여 쓰기 가능한 스트림에 전달하거나 하나를 통해 파이핑할 수 있습니다. 먼저 읽을 수 있는 스트림의 pipeThrough() 메서드를 사용하여 그 이상의 변환 스트림을 시작합니다. 일련의 이런 방식으로 배관된 스트림을 파이프 체인이라고 합니다.

배압

파이프 체인이 구성되면 청크가 얼마나 빨리 흘러야 하는지에 관한 신호를 전파합니다. 그것을 통해. 체인의 단계가 아직 청크를 수락할 수 없는 경우 신호를 역방향으로 전파합니다. 결국에는 원본 소스에 청크 생성을 중지하라는 지시가 내려질 때까지 파이프 체인을 통해 빠릅니다 이 흐름 정규화 프로세스를 백프레셔라고 합니다.

티잉

읽을 수 있는 스트림은 tee() 메서드를 사용하여 teed (대문자 'T' 모양에 따라 이름 지정됨)할 수 있습니다. 스트림이 잠금 상태가 되어 더 이상 직접 사용할 수 없게 됩니다. 대신 새로운 두 개의 브랜치라고 하는 스트림입니다. 이러한 스트림은 독립적으로 사용할 수 있습니다. 스트림을 되감거나 다시 시작할 수 없기 때문에 티잉도 중요합니다. 이 내용은 나중에 자세히 알아봅니다.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">가져오기 API 호출에서 나오는 읽을 수 있는 스트림으로 구성된 파이프 체인의 다이어그램. 출력된 변환 스트림을 통해 파이핑된 후 첫 번째 결과 읽기 가능 스트림의 경우 브라우저로 전송되고 두 번째 결과 읽기 가능 스트림의 경우 서비스 워커 캐시로 전송됩니다.</ph>
파이프 체인

읽을 수 있는 스트림의 메커니즘

읽을 수 있는 스트림은 ReadableStream 객체를 소스부터 시작하겠습니다 이 ReadableStream() 드림 생성자는 지정된 핸들러에서 읽을 수 있는 스트림 객체를 만들고 반환합니다. 두 가지 기본 소스 유형:

  • 푸시 소스는 사용자가 데이터에 액세스했을 때 데이터를 지속적으로 푸시하며 스트림 액세스를 시작, 일시중지 또는 취소할 수 있습니다. 예를 들어 라이브 동영상 스트림, 서버 전송 이벤트, 또는 WebSockets 등이 있습니다
  • 가져오기 소스를 사용하려면 연결되었을 때 명시적으로 데이터를 요청해야 합니다. 예 fetch() 또는 XMLHttpRequest 호출을 통한 HTTP 작업을 포함할 수 없습니다.

스트림 데이터는 청크라고 하는 작은 조각에서 순차적으로 읽힙니다. 스트림에 배치된 청크를 큐에 추가했다고 말합니다. 큐에서 대기 중이라는 의미입니다. 읽을 수 있습니다. 내부 큐는 아직 읽히지 않은 청크를 추적합니다.

큐 전략은 다음에 따라 스트림이 백 프레셔에 신호를 보내는 방법을 결정하는 객체입니다. 내부 큐의 상태입니다. 큐 전략은 각 청크에 크기를 할당하고 큐에 있는 모든 청크의 전체 크기를 높은 워터마크라고 하는 지정된 수로 설정합니다.

스트림 내의 청크는 리더가 읽습니다. 이 리더는 한 번에 한 청크씩 데이터를 검색합니다. 원하는 작업을 할 수 있습니다. 독자와 이와 함께 전달되는 처리 코드를 소비자라고 합니다.

이 컨텍스트의 다음 구성은 컨트롤러라고 합니다. 읽을 수 있는 각 스트림에는 이름에서 알 수 있듯이 스트림을 제어할 수 있는 컨트롤러입니다.

한 번에 하나의 리더만 스트림을 읽을 수 있습니다. 리더가 생성되고 스트림 읽기를 시작할 때 (즉, 활성 리더가 됨) 잠겨 있습니다. 다른 독자에게 계속 전달하고 싶은 경우 일반적으로 다른 작업을 하기 전에 첫 번째 리더를 해제해야 합니다. (스트림을 티핑할 수는 있습니다.)

읽을 수 있는 스트림 만들기

생성자를 호출하여 읽을 수 있는 스트림을 만듭니다. ReadableStream() 생성자에는 객체를 나타내는 선택적 인수 underlyingSource가 있습니다. 생성된 스트림 인스턴스의 동작 방식을 정의하는 메서드와 속성으로 사용합니다.

underlyingSource

다음과 같은 선택적 개발자 정의 메서드를 사용할 수 있습니다.

  • start(controller): 객체가 구성되면 즉시 호출됩니다. 이 해당 메서드는 스트림 소스에 액세스하고 스트림 기능을 설정하는 데 필요합니다. 이 프로세스가 비동기식으로 수행되면 메서드는 프라미스를 반환하여 성공 또는 실패를 알립니다. 이 메서드에 전달되는 controller 매개변수는 다음과 같습니다. a ReadableStreamDefaultController
  • pull(controller): 더 많은 청크를 가져올 때 스트림을 제어하는 데 사용할 수 있습니다. 그것은 스트림의 내부 청크 큐가 가득 차 있지 않으면 큐까지 반복적으로 호출됨 기대감을 높였습니다. pull() 호출의 결과가 프로미스인 경우 해당 프라미스가 이행될 때까지 pull()이(가) 다시 호출되지 않습니다. 프로미스가 거부되면 스트림에 오류가 발생합니다.
  • cancel(reason): 스트림 소비자가 스트림을 취소할 때 호출됩니다.
const readableStream = new ReadableStream({
  start(controller) {
    /* … */
  },

  pull(controller) {
    /* … */
  },

  cancel(reason) {
    /* … */
  },
});

ReadableStreamDefaultController는 다음 메서드를 지원합니다.

/* … */
start(controller) {
  controller.enqueue('The first chunk!');
},
/* … */

queuingStrategy

ReadableStream() 생성자의 두 번째 인수(선택사항)도 queuingStrategy입니다. 이 객체는 스트림의 큐 전략을 선택적으로 정의하는 객체로, 매개변수:

  • highWaterMark: 이 대기열 전략을 사용하는 스트림의 높은 워터마크를 나타내는 음수가 아닌 숫자입니다.
  • size(chunk): 지정된 청크 값의 음수가 아닌 유한한 크기를 계산하여 반환하는 함수입니다. 결과는 배압을 결정하는 데 사용되며, 적절한 ReadableStreamDefaultController.desiredSize 속성을 통해 나타납니다. 또한 기본 소스의 pull() 메서드가 호출되는 시점도 제어합니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.
const readableStream = new ReadableStream({
    /* … */
  },
  {
    highWaterMark: 10,
    size(chunk) {
      return chunk.length;
    },
  },
);

getReader()read() 메서드

읽을 수 있는 스트림에서 읽으려면 ReadableStreamDefaultReader ReadableStream 인터페이스의 getReader() 메서드는 리더를 만들고 스트림을 다음 위치에 잠급니다. 있습니다. 스트림이 잠겨 있는 동안에는 이 기기가 해제될 때까지 다른 리더를 획득할 수 없습니다.

read() ReadableStreamDefaultReader 인터페이스의 메서드가 다음 인스턴스에 대한 액세스를 제공하는 프로미스를 반환합니다. 청크를 생성합니다. 이 함수는 애플리케이션의 상태에 따라 할 수 있습니다. 다른 가능성은 다음과 같습니다.

  • 청크를 사용할 수 있는 경우 프로미스가
    형식의 객체로 처리됩니다. { value: chunk, done: false }
  • 스트림이 닫히면 프로미스가
    형식의 객체로 처리됩니다. { value: undefined, done: true }
  • 스트림에 오류가 발생하면 프로미스가 관련 오류와 함께 거부됩니다.
const reader = readableStream.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) {
    console.log('The stream is done.');
    break;
  }
  console.log('Just read a chunk:', value);
}

locked 속성

다음에 액세스하여 읽을 수 있는 스트림이 잠겼는지 확인할 수 있습니다. ReadableStream.locked 드림 속성

const locked = readableStream.locked;
console.log(`The stream is ${locked ? 'indeed' : 'not'} locked.`);

읽을 수 있는 스트림 코드 샘플

아래의 코드 샘플은 실행 중인 모든 단계를 보여줍니다. 먼저 ReadableStream를 만듭니다. underlyingSource 인수 (즉, TimestampSource 클래스)는 start() 메서드를 정의합니다. 이 메서드는 스트림의 controller에 다음을 지시합니다. enqueue(): 10초 동안 1초마다 타임스탬프 마지막으로, 스트림을 close()하라고 컨트롤러에 지시합니다. 소비할 때마다 getReader() 메서드로 판독기를 만들고 read()을 호출하여 스트림이 done입니다.

class TimestampSource {
  #interval

  start(controller) {
    this.#interval = setInterval(() => {
      const string = new Date().toLocaleTimeString();
      // Add the string to the stream.
      controller.enqueue(string);
      console.log(`Enqueued ${string}`);
    }, 1_000);

    setTimeout(() => {
      clearInterval(this.#interval);
      // Close the stream after 10s.
      controller.close();
    }, 10_000);
  }

  cancel() {
    // This is called if the reader cancels.
    clearInterval(this.#interval);
  }
}

const stream = new ReadableStream(new TimestampSource());

async function concatStringStream(stream) {
  let result = '';
  const reader = stream.getReader();
  while (true) {
    // The `read()` method returns a promise that
    // resolves when a value has been received.
    const { done, value } = await reader.read();
    // 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) return result;
    result += value;
    console.log(`Read ${result.length} characters so far`);
    console.log(`Most recently read chunk: ${value}`);
  }
}
concatStringStream(stream).then((result) => console.log('Stream complete', result));

비동기 반복

스트림이 done인지 각 read() 루프 반복을 확인하는 것은 가장 편리한 API가 아닐 수 있습니다. 다행히 조만간 이 작업을 더 잘 수행하는 더 나은 방법인 비동기 반복이 있을 것입니다.

for await (const chunk of stream) {
  console.log(chunk);
}

현재 비동기 반복을 사용하는 해결 방법은 폴리필을 사용하여 동작을 구현하는 것입니다.

if (!ReadableStream.prototype[Symbol.asyncIterator]) {
  ReadableStream.prototype[Symbol.asyncIterator] = async function* () {
    const reader = this.getReader();
    try {
      while (true) {
        const {done, value} = await reader.read();
        if (done) {
          return;
          }
        yield value;
      }
    }
    finally {
      reader.releaseLock();
    }
  }
}

읽을 수 있는 스트림 티잉

tee() 메서드 ReadableStream 인터페이스가 현재 읽을 수 있는 스트림을 티징하여 두 요소 배열을 반환합니다. 결과 브랜치 2개를 새 ReadableStream 인스턴스로 포함합니다. 이렇게 하면 두 개의 리더가 함께 제공되어 스트림을 동시에 읽을 수 있습니다. 예를 들어 서버에서 응답을 가져와서 브라우저로 스트리밍하고 서비스 워커 캐시입니다. 응답 본문은 두 번 이상 사용할 수 없으므로 두 개의 사본이 필요합니다. 이렇게 할 수 있습니다. 스트림을 취소하려면 결과로 반환되는 두 브랜치를 모두 취소해야 합니다. 스트림 티 하기 일반적으로 그 시간 동안 잠기므로 다른 리더가 잠그지 못합니다.

const readableStream = new ReadableStream({
  start(controller) {
    // Called by constructor.
    console.log('[start]');
    controller.enqueue('a');
    controller.enqueue('b');
    controller.enqueue('c');
  },
  pull(controller) {
    // Called `read()` when the controller's queue is empty.
    console.log('[pull]');
    controller.enqueue('d');
    controller.close();
  },
  cancel(reason) {
    // Called when the stream is canceled.
    console.log('[cancel]', reason);
  },
});

// Create two `ReadableStream`s.
const [streamA, streamB] = readableStream.tee();

// Read streamA iteratively one by one. Typically, you
// would not do it this way, but you certainly can.
const readerA = streamA.getReader();
console.log('[A]', await readerA.read()); //=> {value: "a", done: false}
console.log('[A]', await readerA.read()); //=> {value: "b", done: false}
console.log('[A]', await readerA.read()); //=> {value: "c", done: false}
console.log('[A]', await readerA.read()); //=> {value: "d", done: false}
console.log('[A]', await readerA.read()); //=> {value: undefined, done: true}

// Read streamB in a loop. This is the more common way
// to read data from the stream.
const readerB = streamB.getReader();
while (true) {
  const result = await readerB.read();
  if (result.done) break;
  console.log('[B]', result);
}

읽기 가능한 바이트 스트림

바이트를 나타내는 스트림의 경우 효율적으로 바이트 작업을 실행할 수 있습니다. 바이트 스트림으로 자체 버퍼 사용 가능 (BYOB) 독자를 확보합니다. 기본 구현은 다음과 같은 다양한 출력을 제공할 수 있습니다. WebSocket의 경우 문자열 또는 배열 버퍼로 작동하는 반면 바이트 스트림은 바이트 출력을 보장합니다. 또한 BYOB 리더는 안정성 측면에서 이점이 있습니다. 이것은 버퍼가 분리되면 동일한 버퍼에 두 번 쓰지 않도록 보장할 수 있기 때문입니다. 경합 상태를 피해야 합니다 BYOB 리더는 브라우저를 실행해야 하는 횟수를 줄일 수 있습니다. 가비지 컬렉션을 사용합니다.

읽을 수 있는 바이트 스트림 만들기

추가 type 매개변수를 다음에 전달하여 읽을 수 있는 바이트 스트림을 만들 수 있습니다. ReadableStream() 생성자

new ReadableStream({ type: 'bytes' });

underlyingSource

읽을 수 있는 바이트 스트림의 기본 소스는 ReadableByteStreamController가 제공됩니다. 있습니다. ReadableByteStreamController.enqueue() 메서드는 chunk 인수를 취하며 ArrayBufferView 등급입니다. ReadableByteStreamController.byobRequest 속성은 현재 BYOB pull 요청 또는 없는 경우 null입니다. 마지막으로 ReadableByteStreamController.desiredSize 속성은 제어되는 스트림의 내부 대기열을 채우기 위해 원하는 크기를 반환합니다.

queuingStrategy

ReadableStream() 생성자의 두 번째 인수(선택사항)도 queuingStrategy입니다. 이 객체는 스트림의 큐 전략을 선택적으로 정의하는 객체로, 매개변수:

  • highWaterMark: 이 큐 전략을 사용하는 스트림의 높은 워터마크를 나타내는 음수가 아닌 바이트 수입니다. 이는 적절한 ReadableByteStreamController.desiredSize 속성을 통해 나타나는 배압을 결정하는 데 사용됩니다. 또한 기본 소스의 pull() 메서드가 호출되는 시점도 제어합니다.
를 통해 개인정보처리방침을 정의할 수 있습니다. 를 통해 개인정보처리방침을 정의할 수 있습니다.

getReader()read() 메서드

그런 다음 mode 매개변수를 적절하게 설정하여 ReadableStreamBYOBReader에 액세스할 수 있습니다. ReadableStream.getReader({ mode: "byob" })입니다. 이렇게 하면 버퍼를 보다 정밀하게 제어할 수 있습니다. 복사를 방지하도록 할 수 있습니다. 바이트 스트림에서 읽으려면 다음을 호출해야 합니다. ReadableStreamBYOBReader.read(view), 여기서 viewArrayBufferView

읽기 가능한 바이트 스트림 코드 샘플

const reader = readableStream.getReader({ mode: "byob" });

let startingAB = new ArrayBuffer(1_024);
const buffer = await readInto(startingAB);
console.log("The first 1024 bytes, or less:", buffer);

async function readInto(buffer) {
  let offset = 0;

  while (offset < buffer.byteLength) {
    const { value: view, done } =
        await reader.read(new Uint8Array(buffer, offset, buffer.byteLength - offset));
    buffer = view.buffer;
    if (done) {
      break;
    }
    offset += view.byteLength;
  }

  return buffer;
}

다음 함수는 무작위로 생성된 배열입니다. 미리 정해진 청크 크기인 1,024를 사용하는 대신 개발자가 제공하는 버퍼가 포함되어 있어 완전한 제어가 가능합니다.

const DEFAULT_CHUNK_SIZE = 1_024;

function makeReadableByteStream() {
  return new ReadableStream({
    type: 'bytes',

    pull(controller) {
      // Even when the consumer is using the default reader,
      // the auto-allocation feature allocates a buffer and
      // passes it to us via `byobRequest`.
      const view = controller.byobRequest.view;
      view = crypto.getRandomValues(view);
      controller.byobRequest.respond(view.byteLength);
    },

    autoAllocateChunkSize: DEFAULT_CHUNK_SIZE,
  });
}

쓰기 가능한 스트림의 메커니즘

쓰기 가능한 스트림은 데이터를 쓸 수 있는 대상이며 JavaScript에서 WritableStream 객체 이 기본 싱크(하위 수준 I/O 싱크) 위에 대한 추상화 역할을 합니다. 원시 데이터가 작성됩니다

데이터는 한 번에 한 청크씩 작성기를 통해 스트림에 작성됩니다. 한 청크는 다양한 형태를 가질 수 있습니다. 원하는 모든 코드를 사용하여 작성할 준비가 된 청크 작성자와 관련 코드를 포함하는 요소를 생산자라고 합니다.

작성자가 생성되고 스트림 (활성 작성자)에 쓰기를 시작하면 고정되어 있습니다. 한 번에 하나의 작성자만 쓰기 가능한 스트림에 쓸 수 있습니다. 이 목표를 달성하기 위해 스트림 작성을 시작할 경우 일반적으로 공개해야 하며 만들 수 있습니다.

내부 큐는 스트림에 작성되었지만 아직은 작성되지 않은 청크를 추적합니다. 처리되기 때문입니다.

큐 전략은 다음에 따라 스트림이 백 프레셔에 신호를 보내는 방법을 결정하는 객체입니다. 내부 큐의 상태입니다. 큐 전략은 각 청크에 크기를 할당하고 큐에 있는 모든 청크의 전체 크기를 높은 워터마크라고 하는 지정된 수로 설정합니다.

최종 구성을 컨트롤러라고 합니다. 쓰기 가능한 각 스트림에는 를 사용하여 스트림을 제어 (예: 취소)할 수 있습니다.

쓰기 가능한 스트림 만들기

WritableStream 인터페이스: Streams API는 대상 위치에 스트리밍 데이터를 쓰기 위한 표준 추상화를 제공합니다. 사용하는 것입니다 이 객체에는 기본 제공되는 백프레셔와 큐가 있습니다. 다음과 같이 쓰기 가능한 스트림을 만듭니다. 생성자 호출 WritableStream() 여기에는 객체를 나타내는 underlyingSink 매개변수(선택사항)가 있습니다. 생성된 스트림 인스턴스의 동작 방식을 정의하는 메서드와 속성으로 사용합니다.

underlyingSink

underlyingSink에는 다음과 같은 개발자 정의 메서드(선택사항)가 포함될 수 있습니다. controllerWritableStreamDefaultController

  • start(controller): 이 메서드는 객체가 구성될 때 즉시 호출됩니다. 이 이 메서드의 콘텐츠는 기본 싱크에 액세스하는 것을 목표로 해야 합니다. 이 프로세스가 비동기식으로 수행되면 프라미스를 반환하여 성공 또는 실패를 알릴 수 있습니다.
  • write(chunk, controller): 이 메서드는 ( chunk 매개변수)를 기본 싱크에 쓸 수 있습니다. 프라미스를 반환하여 쓰기 작업의 성공 또는 실패를 알립니다 이 메서드는 이전 호출 이후에만 쓰기가 성공했으며 스트림이 닫히거나 중단된 후에는 되지 않습니다.
  • close(controller): 앱에서 작성을 완료했다고 신호하면 이 메서드가 호출됩니다. 전송합니다. 콘텐츠는 서버에 쓰기를 완료하는 데 필요한 모든 작업을 이에 대한 액세스를 해제합니다 이 프로세스가 비동기식이면 성공 또는 실패를 알릴 것임을 약속하는 것일 수 있습니다. 이 메서드는 큐에 추가된 모든 쓰기 후에만 호출됩니다. 성공했습니다.
  • abort(reason): 앱에서 갑자기 닫으려고 한다고 신호를 보내는 경우 이 메서드가 호출됩니다. 오류 상태가 됩니다. Cloud Shell처럼 보유한 모든 리소스를 정리할 수 있습니다 close(). 쓰기가 큐에 추가된 경우에도 abort()가 호출됩니다. 이러한 청크는 있습니다. 이 프로세스가 비동기식인 경우 프로미스를 반환하여 성공 또는 실패를 알릴 수 있습니다. 이 reason 매개변수에는 스트림이 취소된 이유를 설명하는 DOMString가 포함됩니다.
const writableStream = new WritableStream({
  start(controller) {
    /* … */
  },

  write(chunk, controller) {
    /* … */
  },

  close(controller) {
    /* … */
  },

  abort(reason) {
    /* … */
  },
});

WritableStreamDefaultController 드림 Streams API의 인터페이스는 WritableStream의 상태를 제어할 수 있는 컨트롤러를 나타냅니다. 더 많은 청크가 쓰기를 위해 제출되거나 작성이 끝날 때 발생할 수 있습니다. 구성 시 WritableStream, 기본 싱크에 상응하는 WritableStreamDefaultController가 제공됩니다. 만들 수 있습니다 WritableStreamDefaultController에는 메서드가 하나만 있습니다. WritableStreamDefaultController.error(), 이로 인해 관련 스트림과의 향후 상호작용에 오류가 발생합니다. WritableStreamDefaultController는 다음 인스턴스를 반환하는 signal 속성도 지원합니다. AbortSignal님, 필요한 경우 WritableStream 작업을 중지할 수 있습니다.

/* … */
write(chunk, controller) {
  try {
    // Try to do something dangerous with `chunk`.
  } catch (error) {
    controller.error(error.message);
  }
},
/* … */

queuingStrategy

WritableStream() 생성자의 두 번째 인수(선택사항)도 queuingStrategy입니다. 이 객체는 스트림의 큐 전략을 선택적으로 정의하는 객체로, 매개변수:

  • highWaterMark: 이 대기열 전략을 사용하는 스트림의 높은 워터마크를 나타내는 음수가 아닌 숫자입니다.
  • size(chunk): 지정된 청크 값의 음수가 아닌 유한한 크기를 계산하여 반환하는 함수입니다. 결과는 배압을 결정하는 데 사용되며, 적절한 WritableStreamDefaultWriter.desiredSize 속성을 통해 나타납니다.
를 통해 개인정보처리방침을 정의할 수 있습니다.

getWriter()write() 메서드

쓰기 가능한 스트림에 쓰려면 WritableStreamDefaultWriter WritableStream 인터페이스의 getWriter() 메서드는 다음을 반환합니다. WritableStreamDefaultWriter의 새 인스턴스를 만들고 이 인스턴스로 스트림을 잠급니다. 반면 현재 스트림이 해제될 때까지 다른 작성기를 확보할 수 없습니다.

write() 메서드의 WritableStreamDefaultWriter 인터페이스는 전달된 데이터 청크를 WritableStream 및 기본 싱크에 작성한 다음 쓰기 작업의 성공 또는 실패를 나타내기 위해 확인되는 프로미스입니다. 포드가 '성공' 기본 싱크에 달려 있습니다. 청크가 수락되었음을 나타낼 수도 있습니다. 반드시 최종 목적지에 안전하게 저장되는 것은 아닙니다.

const writer = writableStream.getWriter();
const resultPromise = writer.write('The first chunk!');

locked 속성

쓰기 가능한 스트림이 잠겨 있는지 확인하려면 WritableStream.locked 드림 속성

const locked = writableStream.locked;
console.log(`The stream is ${locked ? 'indeed' : 'not'} locked.`);

쓰기 가능한 스트림 코드 샘플

아래의 코드 샘플은 실행 중인 모든 단계를 보여줍니다.

const writableStream = new WritableStream({
  start(controller) {
    console.log('[start]');
  },
  async write(chunk, controller) {
    console.log('[write]', chunk);
    // Wait for next write.
    await new Promise((resolve) => setTimeout(() => {
      document.body.textContent += chunk;
      resolve();
    }, 1_000));
  },
  close(controller) {
    console.log('[close]');
  },
  abort(reason) {
    console.log('[abort]', reason);
  },
});

const writer = writableStream.getWriter();
const start = Date.now();
for (const char of 'abcdefghijklmnopqrstuvwxyz') {
  // Wait to add to the write queue.
  await writer.ready;
  console.log('[ready]', Date.now() - start, 'ms');
  // The Promise is resolved after the write finishes.
  writer.write(char);
}
await writer.close();

읽을 수 있는 스트림을 쓰기 가능한 스트림으로 파이핑

읽을 수 있는 스트림은 읽을 수 있는 스트림의 pipeTo() 메서드를 사용하여 지도 가장자리에 패딩을 추가할 수 있습니다. ReadableStream.pipeTo()는 현재 ReadableStream를 지정된 WritableStream로 파이핑하고 다음을 반환합니다. 프로미스가 처리되고 오류가 발생한 경우 거부됨 발생할 수 있습니다

const readableStream = new ReadableStream({
  start(controller) {
    // Called by constructor.
    console.log('[start readable]');
    controller.enqueue('a');
    controller.enqueue('b');
    controller.enqueue('c');
  },
  pull(controller) {
    // Called when controller's queue is empty.
    console.log('[pull]');
    controller.enqueue('d');
    controller.close();
  },
  cancel(reason) {
    // Called when the stream is canceled.
    console.log('[cancel]', reason);
  },
});

const writableStream = new WritableStream({
  start(controller) {
    // Called by constructor
    console.log('[start writable]');
  },
  async write(chunk, controller) {
    // Called upon writer.write()
    console.log('[write]', chunk);
    // Wait for next write.
    await new Promise((resolve) => setTimeout(() => {
      document.body.textContent += chunk;
      resolve();
    }, 1_000));
  },
  close(controller) {
    console.log('[close]');
  },
  abort(reason) {
    console.log('[abort]', reason);
  },
});

await readableStream.pipeTo(writableStream);
console.log('[finished]');

변환 스트림 만들기

Streams API의 TransformStream 인터페이스는 변환 가능한 데이터 세트를 나타냅니다. 나 만들고 반환하는 생성자 TransformStream()를 호출하여 변환 스트림을 만듭니다. 변환 스트림 객체를 반환합니다. TransformStream() 생성자는 다음과 같이 수락합니다. 첫 번째 인수는 transformer를 나타내는 선택적 JavaScript 객체입니다. 이러한 객체는 다음 메서드 중 하나를 포함해야 합니다.

transformer

  • start(controller): 이 메서드는 객체가 구성될 때 즉시 호출됩니다. 보통 이는 controller.enqueue()를 사용하여 프리픽스 청크를 큐에 추가하는 데 사용됩니다. 이러한 청크는 쓰기 가능한 쪽에 의존하지 않지만 쓰기 가능한 쪽에 대한 쓰기에 의존하지 않습니다. 만약 이 이니셜이 프로세스는 비동기식입니다. 예를 들어 접두사 청크를 얻는 데 약간의 노력이 필요하기 때문에 함수가 프라미스를 반환하여 성공 또는 실패를 알릴 수 있습니다. 프라미스가 거부되면 있습니다. 발생한 예외는 TransformStream() 생성자에 의해 다시 발생합니다.
  • transform(chunk, controller): 이 메서드는 새 청크가 원래 변환할 준비가 되었음을 나타냅니다. 스트림 구현은 이 함수가 이전 변환이 성공한 후에만 호출되고 start()가 호출되기 전에는 호출되지 않습니다. 완료되거나 flush()가 호출된 후에 시작됩니다. 이 함수는 인코더-디코더 아키텍처를 작업을 수행합니다. controller.enqueue()를 사용하여 결과를 큐에 추가할 수 있습니다. 이 쓰기 가능한 쪽에 기록된 단일 청크가 controller.enqueue()가 호출되는 횟수에 따라 읽기 쉬운 면이 있습니다. 만약 비동기식 변환이므로 이 함수는 프라미스를 반환하여 중요한 역할을 합니다 프라미스가 거부되면 스트림이어야 합니다. transform() 메서드가 제공되지 않으면 ID 변환이 사용됩니다. 쓰기 가능한 측에서 읽기 가능한 쪽으로 변경되지 않은 청크를 큐에 추가합니다.
  • flush(controller): 이 메서드는 쓰기 가능한 측에 작성된 모든 청크가 실행된 후에 호출됩니다. 성공적으로 transform()를 전달하여 변환되고, 쓰기 가능한 쪽은 닫힘 일반적으로 이 방법은 접미사 청크를 읽기 가능한 쪽의 큐에 추가하기 전에도 사용됩니다. 닫힙니다. 플러시 프로세스가 비동기식인 경우 함수는 프라미스를 반환할 수 있습니다. 성공 또는 실패를 알립니다 결과는 호출자에게 전달되며 stream.writable.write() 또한 거부된 프라미스는 읽을 수 있는 것과 쓰기 가능한 측면입니다. 예외를 발생시키는 것은 거부된 결과를 반환하는 것과 동일하게 취급됩니다. 약속하지 않죠.
const transformStream = new TransformStream({
  start(controller) {
    /* … */
  },

  transform(chunk, controller) {
    /* … */
  },

  flush(controller) {
    /* … */
  },
});

writableStrategyreadableStrategy 큐 전략

TransformStream() 생성자의 두 번째 및 세 번째 선택적 매개변수는 선택사항입니다. writableStrategyreadableStrategy 큐 전략 이러한 매개변수는 아래 링크에 설명된 대로 정의됩니다. 읽을 수 있음쓰기 가능 스트림 각 섹션을 참조하세요

변환 스트림 코드 샘플

다음 코드 샘플은 작동 중인 간단한 변환 스트림을 보여줍니다.

// Note that `TextEncoderStream` and `TextDecoderStream` exist now.
// This example shows how you would have done it before.
const textEncoderStream = new TransformStream({
  transform(chunk, controller) {
    console.log('[transform]', chunk);
    controller.enqueue(new TextEncoder().encode(chunk));
  },
  flush(controller) {
    console.log('[flush]');
    controller.terminate();
  },
});

(async () => {
  const readStream = textEncoderStream.readable;
  const writeStream = textEncoderStream.writable;

  const writer = writeStream.getWriter();
  for (const char of 'abc') {
    writer.write(char);
  }
  writer.close();

  const reader = readStream.getReader();
  for (let result = await reader.read(); !result.done; result = await reader.read()) {
    console.log('[value]', result.value);
  }
})();

변환 스트림을 통해 읽을 수 있는 스트림 파이핑

pipeThrough() ReadableStream 인터페이스의 메서드는 현재 스트림을 파이핑하는 체이닝 가능한 방법을 제공합니다. 변환 스트림 또는 기타 쓰기 및 판독 가능 쌍을 통해 전송할 수 있습니다 스트림을 파이핑하면 일반적으로 다른 판독기가 잠그지 못하도록 할 수 있습니다.

const transformStream = new TransformStream({
  transform(chunk, controller) {
    console.log('[transform]', chunk);
    controller.enqueue(new TextEncoder().encode(chunk));
  },
  flush(controller) {
    console.log('[flush]');
    controller.terminate();
  },
});

const readableStream = new ReadableStream({
  start(controller) {
    // called by constructor
    console.log('[start]');
    controller.enqueue('a');
    controller.enqueue('b');
    controller.enqueue('c');
  },
  pull(controller) {
    // called read when controller's queue is empty
    console.log('[pull]');
    controller.enqueue('d');
    controller.close(); // or controller.error();
  },
  cancel(reason) {
    // called when rs.cancel(reason)
    console.log('[cancel]', reason);
  },
});

(async () => {
  const reader = readableStream.pipeThrough(transformStream).getReader();
  for (let result = await reader.read(); !result.done; result = await reader.read()) {
    console.log('[value]', result.value);
  }
})();

다음 코드 샘플 (약간 조잡함)은 '소리치기'를 구현하는 방법을 보여줍니다. fetch() 버전 반환된 응답 프로미스를 소비하고 모든 텍스트를 대문자로 표시 스트림으로 대문자를 청크별로 나누는 것입니다. 이 방법의 장점은 전체 문서가 필요하지 않기 때문에 큰 파일을 다룰 때는 큰 차이가 있을 수 있습니다.

function upperCaseStream() {
  return new TransformStream({
    transform(chunk, controller) {
      controller.enqueue(chunk.toUpperCase());
    },
  });
}

function appendToDOMStream(el) {
  return new WritableStream({
    write(chunk) {
      el.append(chunk);
    }
  });
}

fetch('./lorem-ipsum.txt').then((response) =>
  response.body
    .pipeThrough(new TextDecoderStream())
    .pipeThrough(upperCaseStream())
    .pipeTo(appendToDOMStream(document.body))
);

데모

아래 데모는 읽기, 쓰기 및 변환이 가능한 스트림의 작동 모습을 보여줍니다. 여기에는 예시도 포함되어 있습니다. pipeThrough()pipeTo() 파이프 체인의 그림이며 tee()도 보여줍니다. 원하는 경우 별도의 창에서 데모를 보거나 소스 코드가 있습니다.

브라우저에서 사용할 수 있는 유용한 스트림

브라우저에는 여러 유용한 스트림이 내장되어 있습니다. 사용자는 ReadableStream를 가져옵니다. Blob 인터페이스의 stream() 메서드가 다음을 반환합니다. 읽을 때 blob에 포함된 데이터를 반환하는 ReadableStream 또한 앞서 말씀드린 File 객체는 Blob이며 blob이 사용할 수 있는 모든 컨텍스트에서 사용할 수 있습니다.

const readableStream = new Blob(['hello world'], { type: 'text/plain' }).stream();

TextDecoder.decode()TextEncoder.encode()의 스트리밍 변형은 TextDecoderStream 및 각각 TextEncoderStream입니다.

const response = await fetch('https://streams.spec.whatwg.org/');
const decodedStream = response.body.pipeThrough(new TextDecoderStream());

CompressionStreamDecompressionStream 변환 스트림 각각 1개의 값으로 사용합니다. 아래의 코드 샘플은 스트림 사양을 다운로드하고 압축 (gzip)하는 방법을 보여줍니다. 압축 파일을 디스크에 직접 쓸 수 있습니다.

const response = await fetch('https://streams.spec.whatwg.org/');
const readableStream = response.body;
const compressedStream = readableStream.pipeThrough(new CompressionStream('gzip'));

const fileHandle = await showSaveFilePicker();
const writableStream = await fileHandle.createWritable();
compressedStream.pipeTo(writableStream);

File System Access APIFileSystemWritableFileStream 실험용 fetch() 요청 스트림은 실제 쓰기 가능한 스트림의 예

Serial API는 읽기 및 쓰기 가능한 스트림을 모두 많이 사용합니다.

// Prompt user to select any serial port.
const port = await navigator.serial.requestPort();
// Wait for the serial port to open.
await port.open({ baudRate: 9_600 });
const reader = port.readable.getReader();

// Listen to data coming from the serial device.
while (true) {
  const { value, done } = await reader.read();
  if (done) {
    // Allow the serial port to be closed later.
    reader.releaseLock();
    break;
  }
  // value is a Uint8Array.
  console.log(value);
}

// Write to the serial port.
const writer = port.writable.getWriter();
const data = new Uint8Array([104, 101, 108, 108, 111]); // hello
await writer.write(data);
// Allow the serial port to be closed later.
writer.releaseLock();

마지막으로 WebSocketStream API는 스트림을 WebSocket API와 통합합니다.

const wss = new WebSocketStream(WSS_URL);
const { readable, writable } = await wss.connection;
const reader = readable.getReader();
const writer = writable.getWriter();

while (true) {
  const { value, done } = await reader.read();
  if (done) {
    break;
  }
  const result = await process(value);
  await writer.write(result);
}

유용한 리소스

감사의 말씀

이 기사에 대해 리뷰한 사용자 제이크 아치볼드, 프랑수아 보퍼, 샘 더튼, 마티아스 부엘렌스, Surma, 조 메들리 아담 라이스. 제이크 아치볼드의 블로그 게시물이 있습니다. GitHub 사용자로부터 영감을 받은 일부 코드 샘플 @bellbind의 탐색 분석 및 산문의 일부분이 스트림의 MDN 웹 문서. 이 Streams Standard authors는 방대한 연구 작업을 해냈습니다. 살펴보겠습니다 Ryan Lara가 게시한 히어로 이미지 스플래시 해제.