취소 가능한 가져오기

Jake Archibald
Jake Archibald

'가져오기 취소'에 관한 GitHub의 최초 문제는 2015년에 문을 열었습니다. 이제 2017년 (올해)에서 2015년을 빼앗아가면 2점을 얻습니다. 이는 2015년은 사실 "영원"이었기 때문에 계산에 합니다.

2015년은 진행 중인 가져오기를 중단하는 방법을 연구하기 시작했을 때였고, 780개의 GitHub 코멘트 이후 두 번 잘못 시작했고 5번의 가져오기 요청을 보냈는데, 마침내 취소 가능한 가져오기가 브라우저에 랜딩하게 되었습니다. 첫 번째는 Firefox 57입니다.

업데이트: 틀렸습니다. Edge 16이 낙태 지원과 함께 먼저 착륙했어요! 축하합니다. 에지팀입니다!

나중에 역사에 대해 자세히 알아보겠지만 먼저 API부터 살펴보겠습니다.

컨트롤러 + 신호 조작

AbortControllerAbortSignal를 소개합니다.

const controller = new AbortController();
const signal = controller.signal;

컨트롤러에는 하나의 메서드만 있습니다.

controller.abort();

이렇게 하면 신호에 알립니다.

signal.addEventListener('abort', () => {
    // Logs true:
    console.log(signal.aborted);
});

이 API는 DOM 표준에서 제공하며 전체 API입니다. 그것은 의도적으로 일반적이기 때문에 다른 웹 표준 및 JavaScript 라이브러리에서 사용할 수 있습니다.

신호 취소 및 가져오기

가져오기에는 AbortSignal가 걸릴 수 있습니다. 예를 들어, 5분 후 가져오기 제한 시간을 설정하는 방법은 다음과 같습니다 초:

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => controller.abort(), 5000);

fetch(url, { signal }).then(response => {
    return response.text();
}).then(text => {
    console.log(text);
});

가져오기를 취소하면 요청과 응답이 모두 취소되므로 응답 본문 읽기가 모두 중단됩니다. (예: response.text())도 취소됩니다.

데모 보기 – 이 문서 작성 당시에는 이 것은 Firefox 57입니다. 또한 디자인 스킬을 가진 사람이 전혀 관여하지 않으므로 확인할 수 있습니다

또는 신호를 요청 객체에 제공한 다음 나중에 가져와서 가져올 수 있습니다.

const controller = new AbortController();
const signal = controller.signal;
const request = new Request(url, { signal });

fetch(request);

이는 request.signalAbortSignal이므로 작동합니다.

취소된 가져오기에 반응

비동기 작업을 취소하면 프로미스가 AbortError라는 DOMException와 함께 거부됩니다.

fetch(url, { signal }).then(response => {
    return response.text();
}).then(text => {
    console.log(text);
}).catch(err => {
    if (err.name === 'AbortError') {
    console.log('Fetch aborted');
    } else {
    console.error('Uh oh, an error!', err);
    }
});

사용자가 작업을 중단한 경우 오류 메시지가 표시되지 않기를 원하는 경우가 많습니다. '오류' 사용자가 요청한 작업을 성공적으로 실행한 경우입니다 이를 방지하려면 다음과 같은 if 문을 사용합니다. 취소 오류를 구체적으로 처리해야 합니다.

다음은 사용자에게 콘텐츠를 로드하는 버튼과 취소를 위한 버튼을 제공하는 예입니다. 가져오기가 완료되면 중단 오류가 아닌 경우 오류가 표시됩니다.

// This will allow us to abort the fetch.
let controller;

// Abort if the user clicks:
abortBtn.addEventListener('click', () => {
    if (controller) controller.abort();
});

// Load the content:
loadBtn.addEventListener('click', async () => {
    controller = new AbortController();
    const signal = controller.signal;

    // Prevent another click until this fetch is done
    loadBtn.disabled = true;
    abortBtn.disabled = false;

    try {
    // Fetch the content & use the signal for aborting
    const response = await fetch(contentUrl, { signal });
    // Add the content to the page
    output.innerHTML = await response.text();
    }
    catch (err) {
    // Avoid showing an error message if the fetch was aborted
    if (err.name !== 'AbortError') {
        output.textContent = "Oh no! Fetching failed.";
    }
    }

    // These actions happen no matter how the fetch ends
    loadBtn.disabled = false;
    abortBtn.disabled = true;
});

데모 – 이 문서를 작성한 시점을 기준으로 Edge 16과 Firefox 57을 지원합니다.

하나의 신호, 많은 가져오기

단일 신호를 사용하여 여러 가져오기를 한 번에 취소할 수 있습니다.

async function fetchStory({ signal } = {}) {
    const storyResponse = await fetch('/story.json', { signal });
    const data = await storyResponse.json();

    const chapterFetches = data.chapterUrls.map(async url => {
    const response = await fetch(url, { signal });
    return response.text();
    });

    return Promise.all(chapterFetches);
}

위의 예에서는 최초 가져오기와 병렬 챕터에 동일한 신호가 사용되었습니다. 있습니다. fetchStory를 사용하는 방법은 다음과 같습니다.

const controller = new AbortController();
const signal = controller.signal;

fetchStory({ signal }).then(chapters => {
    console.log(chapters);
});

이 경우 controller.abort()를 호출하면 진행 중인 가져오기가 취소됩니다.

앞으로

기타 브라우저

Edge는 이를 먼저 선보였고 Firefox도 좋은 활약을 펼치고 있습니다. 엔지니어 테스트 모음에서 구현되었으며, 있습니다. 다른 브라우저의 경우 다음과 같은 티켓을 준수해야 합니다.

서비스 워커에서

서비스 워커 부품의 사양을 완료해야 하지만 계획은 다음과 같습니다.

앞서 언급했듯이 모든 Request 객체에는 signal 속성이 있습니다. 서비스 워커 내에서, fetchEvent.request.signal는 페이지가 더 이상 응답에 관심이 없다면 취소를 알립니다. 따라서 다음과 같은 코드가 작동합니다.

addEventListener('fetch', event => {
    event.respondWith(fetch(event.request));
});

페이지에서 가져오기를 취소하면 fetchEvent.request.signal가 취소 신호를 보내서 서비스 워커도 중단됩니다

event.request이 아닌 다른 것을 가져오는 경우 신호를 커스텀 가져오기를 사용할 수 있습니다.

addEventListener('fetch', event => {
    const url = new URL(event.request.url);

    if (event.request.method == 'GET' && url.pathname == '/about/') {
    // Modify the URL
    url.searchParams.set('from-service-worker', 'true');
    // Fetch, but pass the signal through
    event.respondWith(
        fetch(url, { signal: event.request.signal })
    );
    }
});

사양에 따라 추적 – 제가 링크를 추가하겠습니다. 브라우저 티켓을 호출합니다.

역사

맞아... 비교적 간단한 이 API를 합치는 데는 오랜 시간이 걸렸어요. 그 이유는 다음과 같습니다.

API 불일치

보시다시피 GitHub 토론은 꽤 깁니다. 해당 대화목록에는 미묘한 차이가 있고 약간의 뉘앙스가 부족하지만, 주된 불일치는 그룹은 fetch()에서 반환된 객체에 abort 메서드가 존재하길 원했던 반면 응답을 받는 것과 응답에 영향을 미치는 것의 구분을 원했습니다.

이러한 요구사항은 서로 호환되지 않으므로 한 그룹은 원하는 것을 얻지 못했습니다. 만약 죄송합니다. 기분이 나아진다면 저도 그 그룹에 속해 있었습니다. 하지만 AbortSignal를 보면 적절한 선택처럼 보일 수 있습니다 또한 체인된 프라미스를 허용하여 낙태가 불가능하지는 않더라도 매우 복잡해질 수 있습니다.

응답을 제공하지만 취소할 수도 있는 객체를 반환하려는 경우 간단한 래퍼:

function abortableFetch(request, opts) {
    const controller = new AbortController();
    const signal = controller.signal;

    return {
    abort: () => controller.abort(),
    ready: fetch(request, { ...opts, signal })
    };
}

TC39에서 거짓 시작

취소된 작업과 오류를 구분하기 위해 노력했습니다. 여기에는 Ad Exchange의 'cancelled'를 나타내는 상태 및 동기화 및 비동기에서 취소를 처리하는 몇 가지 새로운 문법 코드:

금지사항

진짜 코드가 아님 — 제안이 철회됨

    try {
      // Start spinner, then:
      await someAction();
    }
    catch cancel (reason) {
      // Maybe do nothing?
    }
    catch (err) {
      // Show error message
    }
    finally {
      // Stop spinner
    }

작업이 취소되었을 때 가장 일반적으로 하는 일은 아무것도 하지 않는 것입니다. 위 제안서는 취소가 불가능하므로 취소 오류를 특별히 처리할 필요가 없었습니다. catch cancel 허용 취소된 작업에 대해 듣게 되지만 대부분의 경우에는 필요하지 않습니다.

이는 TC39의 1단계에 도달했지만 합의에 도달하지 못하여 제안이 철회되었습니다.

대체 제안인 AbortController에는 새로운 구문이 필요하지 않아 적절하지 않았습니다. TC39 내에서 지정합니다. JavaScript에 필요한 모든 것이 이미 존재했기 때문에 인터페이스, 특히 DOM 표준의 인터페이스가 제공됩니다. 일단 결정을 내리면 나머지는 비교적 빨리 합쳐졌습니다.

사양이 크게 변경됨

XMLHttpRequest는 수년 동안 중단될 수 있었지만 사양이 상당히 모호했습니다. 명확하지 않았음 기본 네트워크 활동이 회피되거나 종료될 수 있는 지점, 또는 만약 기본 네트워크 활동이 abort()가 호출되는 시점과 가져오기 완료 사이에 경합 상태가 발생했습니다.

제대로 된 결과를 얻고 싶었지만 그 결과 대대적인 사양 변경이 필요하게 되었습니다. 제 잘못입니다. Anne van Kesteren님과 Domenic Denicola는 저를 끌어 들이고, 적절한 테스트 세트를 사용했습니다.

하지만 이제 여기 있습니다. 비동기 작업을 중단하기 위한 새로운 웹 프리미티브가 있으며, 여러 개의 가져오기가 한 번에 관리할 수 있습니다. 더 나아가 가져오기의 수명 동안 우선순위를 변경하도록 설정하는 방법과 더 높은 수준의 가져오기 진행 상황을 관찰하는 API입니다.