Как правильно очистить useEffect, содержащий асинхронную операцию graphql

Я использую graphql/apollo и реагирую.

У меня есть следующий код

  const [state, setState] = useState(undefined);
  useEffect(() => {
    (async () => {
      try {
        const workspace = await getFirstWorkspace();
        // Do Something
        setState(withSomething)
      } catch (error) {
        // Do Something Else
        setState(withErrorSomething)
      }
    })();
  }, [generateLink, getFirstWorkspace, masterDataStoreId]);

теперь это работало нормально, пока я не обновил некоторые пакеты, в настоящее время я получаю эту ошибку.

Uncaught (в обещании) DOMException: сигнал прерывается без причины

Насколько я понимаю, мой useEffect выдает это, когда компонент размонтирован, а запрос не завершен.

Теперь это приводит к тому, что мой улов всегда срабатывает хотя бы один раз, потому что похоже, что когда эффект запускается снова, потому что одно из изменений изменилось, оно терпит неудачу.

Я """ исправил """ это, выполнив

  const [state, setState] = useState(undefined);
  useEffect(() => {
    (async () => {
      try {
        const workspace = await getFirstWorkspace();
        // Do Something
        setState(withSomething)
      } catch (error) {
        // Do Something Else
        if ((error as any)?.name === 'AbortError') {
          return;
        }
        setState(withErrorSomething)
      }
    })();
  }, [generateLink, getFirstWorkspace, masterDataStoreId]);

И не назначать какое-либо состояние в случае, если ошибка прерывается. Но я не смог найти подходящего решения или не понимаю, почему это было проблематично раньше, а не сейчас, я обновил какой-то пакет, но ни один не упомянул об изменении поведения на этом конце.

Мой вопрос, что я должен сделать, чтобы сделать все правильно?

«Насколько я понимаю, мой useEffect выдает это, когда компонент размонтирован, а выполнение запроса не завершено». Что заставляет вас думать, что? У вас есть ссылка на это? Я никогда не видел эту ошибку от React. Похоже на что-то другое.

T.J. Crowder 08.02.2023 10:13

удаление обещания из эффекта удалить эту ошибку. В основном все useEffect, содержащие асинхронную операцию, теперь вызывают эту ошибку при изменении страницы. Это может быть связано с каким-то graphql обновлением, или router обновлением, или useEffect ими самими. Но я не смог найти хорошего решения для работы с этим, и возврат graphql и реакция маршрутизатора dom на предыдущие версии по-прежнему вызывают эту ошибку, поэтому я действительно не понимаю, откуда она взялась.

Bobby 08.02.2023 10:48
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
76
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я не думаю, что указанная вами ошибка исходит от React. Раньше React жаловался, если вы обновили состояние в компоненте, который больше не был смонтирован, но сообщение об ошибке, которое он использовал, было «Невозможно выполнить обновление состояния React для несмонтированного компонента. Это не работает, но указывает утечка памяти в вашем приложении». Но последние версии React этого не делают, потому что команда React решила, что это слишком суетливо.

Тем не менее, отвечая на заданный вопрос:

  1. Если getFirstWorkspace предлагает способ отменить то, что он делает, вы бы это сделали. Например, если он поддерживает AbortSignal, вы можете сделать это:

    useEffect(() => {
        // *** Create a controller and get its signal
        const controller = new AbortController();
        const { signal } = controller;
        (async () => {
            try {
                // *** Pass the signal to `getFirstWorkspace`
                const workspace = await getFirstWorkspace(signal);
                // *** Only do something if the signal isn't aborted
                if (!signal.aborted) {
                    // Do Something
                    setState(withSomething);
                }
            } catch (error) {
                // *** Only do something if the signal isn't aborted
                if (!signal.aborted) {
                    // Do Something Else
                    setState(withErrorSomething);
                }
            }
        })();
        return () => {
            // *** Abort the signal on cleanup
            controller.abort();
        };
    }, [generateLink, getFirstWorkspace, masterDataStoreId]);
    

    ... или аналогичный, если он не поддерживает специально AbortSignal, но предоставляет какой-либо другой способ отмены своей работы.

  2. Если это не так, вы можете вернуться к флагу, говорящему вам не использовать результат:

    useEffect(() => {
        // *** Start with a flag set to `false`
        let cancelled = false;
        (async () => {
            try {
                const workspace = await getFirstWorkspace();
                // *** Only do something if the flag is still `false`
                if (!cancelled) {
                    // Do Something
                    setState(withSomething);
                }
            } catch (error) {
                // *** Only do something if the flag is still `false`
                if (!cancelled) {
                    // Do Something Else
                    setState(withErrorSomething);
                }
            }
        })();
        return () => {
            // *** Set the flag on cleanup
            cancelled = true;
        };
    }, [generateLink, getFirstWorkspace, masterDataStoreId]);
    

На самом деле лучше отменить работу, если вы можете, но вполне нормально иметь резервное логическое значение, если вы не можете. Только не думайте, что вы не можете, обязательно сначала проверьте. :-)


Примечание: я люблю async/await, но когда вы делаете всего один вызов и получаете обещание, создание оболочки async и try/catch вокруг await может быть немного излишним. FWIW, просто прямое использование обещания выглядит так (в этом случае используется флаг, но он работает так же хорошо с контроллером/сигналом):

useEffect(() => {
    let cancelled = false;
    getFirstWorkspace().then(
        (workspace) => {
            if (!cancelled) {
                // Do Something
                setState(withSomething);
            }
        },
        (error) => {
            if (!cancelled) {
                // Do Something Else
                setState(withErrorSomething);
            }
        }
    );
    return () => {
        cancelled = true;
    };
}, [generateLink, getFirstWorkspace, masterDataStoreId]);

Вы могли бы использовать AbortController

  const [state, setState] = useState(undefined);
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;
    (async () => {
      try {
        const workspace = await getFirstWorkspace(signal);
        // Do Something
        setState(withSomething)
      } catch (error) {
        // Do Something Else
        setState(withErrorSomething)
      }
    })();
    return =()=>Controller.abort();
  }, [generateLink, getFirstWorkspace, masterDataStoreId]);

Другие вопросы по теме