JavaScript에서 취소 가능한 비동기 지연 구현

11066 단어 promiseasyncjavascript
최근 React 프로젝트에서 작업하는 동안 fetch() 을 사용하여 검색된 API의 데이터로 일부 상태를 주기적으로 업데이트해야 했습니다. C# 배경에서 이 문제에 접근하는 방식은 다음과 같습니다.

private async Task FetchDataContinuouslyAsync(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        await FetchDataAndSetStateAsync(cancellationToken);

        // now wait for 15 seconds before trying again
        await Task.Delay(15000, cancellationToken);
    }
}


당연히 JavaScript에서도 같은 방식으로 문제에 접근했습니다. 그것이 내가 걸림돌을 치는 곳입니다. Task.Delay() 과 유사한 내장 기능이 없습니다.

이것은 내가 문제에 대한 내 자신의 해결책을 생각해 내야 한다는 것을 의미했습니다. 인터넷을 검색하면 사람들이 setTimeout 과 함께 Promise 을 사용하는 많은 결과가 나왔지만 놀랍게도 조기 취소를 지원하는 사람은 거의 없었고 취소에 대한 토큰을 관찰하기보다 취소 기능을 반환하는 경향이 있었습니다. 이미 요청을 취소하기 위해 fetch() 과 함께 AbortController를 사용하고 있었기 때문에 취소를 위해 해당 컨트롤러를 다시 사용하고 싶었습니다.

내가 생각해 낸 것은 다음과 같습니다.

/**
 * Return a promise that is resolved after a given delay, or after being cancelled.
 * 
 * @param  {number} duration The delay, in milliseconds.
 * @param  {AbortSignal|null} signal An optional AbortSignal to cancel the delay.
 * 
 * @return {Promise<void>} A promise that is either resolved after the delay, or rejected after the signal is cancelled.
 */
function asyncSleep(duration, signal) {
    function isAbortSignal(val) {
        return typeof val === 'object' && val.constructor.name === AbortSignal.name;
    }

    return new Promise(function (resolve, reject) {
        let timeoutHandle = null;

        function handleAbortEvent() {        
            if (timeoutHandle !== null) {
                clearTimeout(timeoutHandle);
            }

            if (signal !== null && isAbortSignal(signal)) {
                signal.removeEventListener('abort', handleAbortEvent);
            }

            reject(new DOMException('Sleep aborted', 'AbortError'));
        }

        if (signal !== null && isAbortSignal(signal)) {
            signal.addEventListener('abort', handleAbortEvent);
        }

        timeoutHandle = setTimeout(function () {        
            if (signal !== null && isAbortSignal(signal)) {
                signal.removeEventListener('abort', handleAbortEvent);
            }

            resolve();
        }, duration);
    });
}


이 함수는 첫 번째 매개변수로 밀리초 단위의 지연을 사용하고 두 번째 매개변수로 선택적AbortSignal을 사용합니다. 지정된 지연 후 해결되거나 취소가 요청된 경우 Promise<void>로 거부되는 AbortError를 반환합니다.

React 프로젝트의 맥락에서 이것은 useEffect 후크 내에서 다음과 같이 사용될 수 있습니다.

useEffect(() => {
    const ac = new AbortController();

    async function fetchDataContinuously(abortController) {
        while (!abortController.signal.aborted) {
            try {
                await getData(abortController.signal);

                await asyncSleep(refreshInterval, abortController.signal);
            } catch (e) {
                if (e.name === 'AbortError') {
                    break;
                }

                console.error('Error continuously refreshing', e);
            }
        }
    }

    fetchDataContinuously(ac).catch(console.error);

    return () => {
        ac.abort();
    };
}, []);


물론 이것은 AbortController에서 componentWillUnmount를 단순히 중단함으로써 전통적인 클래스 기반 React 구성 요소와 함께 사용할 수도 있습니다.

좋은 웹페이지 즐겨찾기