Wątek przeniesiony 2024-01-21 18:39 z JavaScript przez Riddle.

Właściwy sposób na uzycie fetch AbortControllera

0

Jest sobie funkcja wykonująca asynchroniczną operację pobierania danych, która jeszcze do niedawna gnieździła się spokojnie w podstawowym flow po naciśnięciu guzika, działała i nie wadziła nikomu. Niemniej poczytałam co nieco i wniosek był taki aby przenieść jej wywołanie do useEffect(). OK. Niby jasne. I żeby w tym useEffect() był zwracany cleanup. To w zasadzie też jasne choć nieco mniej (nie jarzyłam co taka funkcja konkretnie miałaby robić w wypadku fetcha a wiedza z timerów była nizbyt przydatna. Wreszcie zaskoczyło - fetch AbortController. Pytanie tylko czy uzywam go prawidłowo. Żródło, z którego czerpałam wiedzę to https://hackernoon.com/cleanup-functions-in-reacts-useeffect-hook-explained
niby fajne bo proste, ale pozostawia pewne wątpliwości.
Tak wygląda effect:

React.useEffect(() => {
    let controller = new AbortController();
    if (URL) {
        fetchBooksFromAPI(URL, controller);
    }
    return () => controller?.abort();
}, [URL, fetchBooksFromAPI]);

A tak hook, gdzie tworzona jest funkcja fetchBooksFromAPI(). Dla czytelności wywaliłam treści z kilku callbacków - one działają jak trzeba. Żeby było ciekawiej, tu jest rekursja, nie wiem a priori ile danych jest do pobrania, a ilość rekordów w jednorazowym fetchu ograniczona. Właściwą robotę robi funkcja recursiveFetch. Zaznaczyłam miejsca gdzie MOŻE zabicie powinno się znaleźć. Poza tym pytanie - czy to powinno być zabite przez controller = null, czy przez controller?.abort()? W artykule są użyte obie wersje, fakt, że w dwóch różnych kontekstach ale różnica jest dla mnie wątpliwa.

export const useFetchBooks = () => {
    const navigate = useNavigate();
    const showMessage = useMessage();
    const { setIsLoading, showError, fetchBooks, setIsFromNetwork } = useDispatchAction();

    const fetchBooksFromAPI = (path: string, controller: AbortController) => {
        let startIndex = 0;
        let foundBooks = INITIAL_FOUND_BOOKS;
        let fetchSummary = INITIAL_FETCH_SUMMARY;
        setIsLoading(true);

        const handleError = (response: any) => {
            
        };
        const handleNotFound = () => {
            
        };

        const handleSuccess = (foundBooks: FoundBooks) => {
        /// druga propozycja - zabić w tym miejscu. Ale jeżeli w tym to dlaczego nie we wszystkich callbackach kończących jakąś gałąź fetcha?
            
        };

        const increaseCounter = () => {
            
        };

        const addBooks = (moreBooks: any) => {
           
        };

        async function recursiveFetch() {
            const fullPath = path + startIndex.toString();
            const fetchResult = await fetch(fullPath, { signal: controller.signal }).catch(error => {
                handleError(error);
            });
            if (fetchResult) {
                const response = await fetchResult.json();
                /// pierwsza propozycja - zabić w tym miejscu
                const code = getValue(response, "code");
                if (isErrorCode(code)) {
                    handleError(response);
                    return;
                }

                if (response.items) {
                    increaseCounter();
                    addBooks(response.items);
                    recursiveFetch();
                } else {
                    if (response.error) {
                        handleError(response);
                    }

                    if (!response.eror && foundBooks.length === 0) {
                        handleNotFound();
                    } else {
                        handleSuccess(foundBooks);
                    }
                }
            }
        }
        recursiveFetch();
    };

    return React.useMemo(() => fetchBooksFromAPI, []); // musi być zmemoizowane, jeżeli ma się pojawić jako dependencja
};

Doprawdy, nie wiem jak należałoby to zrobić

0

ZdecydowanieAbortController.abort() nie ma sensu korzystać z jakiś hacków. https://developer.mozilla.org/en-US/docs/Web/API/AbortController
Nie ma znaczenia gdzie zdecydujesz się na abort bo przekazujesz ten sam signal do funkcji fetch. W momencie, w którym wywołasz Abort zostanie rzucony wyjątek.

Note: When abort() is called, the fetch() promise rejects with an Error of type DOMException, with name AbortError.

0

Jak to zrobić w kilku krokach:

  1. Napisz w świeżym projekcie (bez reacta, sam js) fetch() oraz zrób na nim aborta.
  2. W projekcie reactowym dodaj useEffect() który ma clean up - możesz w nich wywołać nawet console.log()
  3. Połącz te dwa podejścia.

1 użytkowników online, w tym zalogowanych: 0, gości: 1