스트리밍으로 즉각적으로 반응하기

서비스 워커를 사용해 본 사람은 누구나 서비스 워커가 완전히 비동기식이라고 말할 수 있습니다. FetchEvent와 같은 이벤트 기반 인터페이스에만 의존하며 약속을 사용하여 비동기 작업이 완료되었음을 알립니다.

비동기화는 서비스 워커의 가져오기 이벤트 핸들러에서 제공하는 응답과 관련하여 개발자에게는 덜 눈에 띄지만 동일하게 중요합니다. 스트리밍 응답이 여기서 표준입니다. 원래 요청을 한 페이지가 첫 번째 데이터 청크를 사용할 수 있는 즉시 응답 작업을 시작할 수 있으며, 스트리밍에 최적화된 파서를 사용하여 콘텐츠를 점진적으로 표시할 수 있습니다.

자체 fetch 이벤트 핸들러를 작성할 때는 일반적으로 fetch() 또는 caches.match()를 통해 가져온 Response(또는 Response의 약속)를 respondWith() 메서드에 전달하고 종료합니다. 다행히 두 메서드로 생성된 Response는 이미 스트리밍할 수 있습니다. 나쁜 소식은 '수동으로' 구성Response는 적어도 지금까지는 스트리밍할 수 없다는 것입니다. 여기서 Streams API가 등장합니다.

스트림

스트림은 증분 방식으로 생성 및 조작할 수 있는 데이터 소스이며 비동기 데이터 청크를 읽거나 쓰기 위한 인터페이스를 제공합니다. 이때 특정 시점에 메모리에서 사용할 수 있는 데이터는 하위 집합일 수 있습니다. 지금은 fetchEvent.respondWith()에 전달되는 Response 객체를 구성하는 데 사용할 수 있는 ReadableStream에 관심이 있습니다.

self.addEventListener('fetch', event => {
    var stream = new ReadableStream({
    start(controller) {
        if (/* there's more data */) {
        controller.enqueue(/* your data here */);
        } else {
        controller.close();
        }
    });
    });

    var response = new Response(stream, {
    headers: {'content-type': /* your content-type here */}
    });

    event.respondWith(response);
});

요청으로 인해 fetch 이벤트가 트리거된 페이지는 event.respondWith()이 호출되는 즉시 스트리밍 응답을 다시 가져오고 서비스 워커가 추가 데이터를 계속 enqueue()하는 한 해당 스트림에서 계속 읽습니다. 서비스 워커에서 페이지로 전송되는 응답은 완전히 비동기식이며 스트림 채우기를 완전히 제어할 수 있습니다.

실제 사용 사례

이전 예에는 자리표시자 /* your data here */ 주석이 몇 개 있었고 실제 구현 세부정보는 간단했습니다. 실제 사례는 어떤 모습일까요?

Jake Archibald은 스트림을 사용하여 캐시된 여러 HTML 스니펫의 HTML 응답과 fetch()를 통해 스트리밍된 '실시간' 데이터 (이 경우 블로그의 콘텐츠)를 함께 연결하는 훌륭한 예를 제공합니다.

제이크가 설명한 대로 스트리밍 응답을 사용하면 전체 블로그 콘텐츠 가져오기가 완료될 때까지 기다릴 필요 없이 브라우저가 캐시에서 빠르게 로드되는 초기 비트를 포함하여 HTML이 스트리밍될 때 이를 파싱하고 렌더링할 수 있다는 이점이 있습니다. 이렇게 하면 브라우저의 프로그레시브 HTML 렌더링 기능을 최대한 활용할 수 있습니다. 일부 이미지 및 동영상 형식과 같이 점진적으로 렌더링할 수 있는 다른 리소스도 이 접근 방식의 이점을 누릴 수 있습니다.

스트림 아니면 앱 셸인가요?

서비스 워커를 사용하여 웹 앱을 지원하는 것과 관련된 기존 권장사항은 앱 셸 + 동적 콘텐츠 모델에 중점을 둡니다. 이 접근 방식은 웹 애플리케이션의 '셸'(구조와 레이아웃을 표시하는 데 필요한 최소한의 HTML, JavaScript, CSS)을 공격적으로 캐시한 다음 클라이언트 측 요청을 통해 각 페이지에 필요한 동적 콘텐츠를 로드하는 데 의존합니다.

스트림은 앱 셸 모델의 대안을 제공합니다. 이 모델에서는 사용자가 새 페이지로 이동할 때 브라우저로 더 완전한 HTML 응답이 스트리밍됩니다. 스트리밍된 응답은 캐시된 리소스를 활용할 수 있으므로 오프라인 상태에서도 초기 HTML 덩어리를 빠르게 제공할 수 있습니다. 하지만 기존의 서버 렌더링 응답 본문과 더 유사하게 표시됩니다. 예를 들어 웹 앱이 부분 템플릿을 연결하여 HTML을 서버 렌더링하는 콘텐츠 관리 시스템을 기반으로 하는 경우, 이 모델은 스트리밍 응답 사용으로 직접 변환되며 템플릿 로직은 서버 대신 서비스 워커에 복제됩니다. 다음 동영상에서 볼 수 있듯이 이러한 사용 사례의 경우 스트리밍된 응답이 제공하는 속도 이점이 상당할 수 있습니다.

전체 HTML 응답을 스트리밍하는 한 가지 중요한 이점은 이 방법이 동영상에서 가장 빠른 대안인 이유를 설명하는 것으로, 초기 탐색 요청 중에 렌더링된 HTML이 브라우저의 스트리밍 HTML 파서를 최대한 활용할 수 있다는 것입니다. 페이지가 로드된 후에 문서에 삽입되는 HTML 청크 (앱 셸 모델에서 일반적임)는 이 최적화를 활용할 수 없습니다.

따라서 서비스 워커 구현을 계획하고 있다면 점진적으로 렌더링되는 스트리밍 응답과 동적 콘텐츠에 대한 클라이언트 측 요청과 결합된 경량 셸 중 어떤 모델을 채택해야 할까요? 놀랍지 않게도 답변은 CMS 및 부분 템플릿을 사용하는 기존 구현이 있는지 (스트림의 이점), 점진적 렌더링의 이점을 누릴 수 있는 단일 대규모 HTML 페이로드가 예상되는지 (스트림의 이점), 웹 앱을 싱글페이지 애플리케이션으로 모델링하는 것이 가장 좋은지 (앱 셸의 이점), 현재 여러 브라우저의 안정적인 출시에서 지원되는 모델이 필요한지 (앱 셸의 이점)에 따라 달라집니다.

아직 서비스 워커 기반 스트리밍 응답이 초기 단계에 있으므로 다양한 모델이 성숙해지고 특히 일반적인 사용 사례를 자동화하기 위해 더 많은 도구가 개발되기를 기대합니다.

스트림 자세히 알아보기

읽을 수 있는 자체 스트림을 구성하는 경우 무차별적으로 controller.enqueue()를 호출하는 것만으로는 충분하지 않거나 효율적이지 않을 수 있습니다. 제이크는 start(), pull(), cancel() 메서드를 함께 사용하여 사용 사례에 맞는 데이터 스트림을 만드는 방법을 자세히 설명합니다.

더 자세한 내용은 스트림 사양을 참고하세요.

호환성

소스로 ReadableStream를 사용하여 서비스 워커 내에서 Response 객체를 구성하는 지원이 Chrome 52에서 추가되었습니다.

Firefox의 서비스 워커 구현은 아직 ReadableStream로 지원되는 응답을 지원하지 않지만, Streams API 지원과 관련된 추적 버그가 있으므로 이를 참고하세요.

Edge에서 접두사가 없는 Streams API 지원과 전반적인 서비스 워커 지원의 진행 상황은 Microsoft의 플랫폼 상태 페이지에서 추적할 수 있습니다.