Рубрики
Без рубрики

Почему голые обещания не безопасны для работы – а что вместо этого

Эта статья проходит через мое личное путешествие на открытие и борьбу, принимая обычную мудрость, поскольку она относится к асинхронной работе на фантазии. С любой удачей вы уйдете по крайней мере более глубокую признательность за 3 хитрых случаев для обработки при пересечении синхронного до асинхронной границы.

Автор оригинала: FreeCodeCamp Community Member.

Эта статья проходит через мое личное путешествие на открытие и борьбу, принимая обычную мудрость, поскольку она относится к асинхронной работе на фантазии. С любой удачей вы уйдете по крайней мере более глубокую признательность за 3 хитрых случаев для обработки при пересечении синхронного до асинхронной границы. И мы, возможно, даже сделать вывод, что вы никогда не захотите вручную объяснить эти краевые случаи.

Мои примеры реагируют, но я считаю, что они являются универсальными принципами, которые имеют параллели во всех приложениях Frontend.

Какое «голое обещание» в любом случае?

Чтобы сделать что-нибудь интересное в нашем приложении, мы, вероятно, будем использовать асинхронный API в какой-то момент. В JavaScript обещания обгоняли обратные вызовы, чтобы быть асинхронным API выбора (особенно поскольку каждая платформа пришла к Async / ждут ). Они даже стали частью «веб-платформы» - вот типичный пример, используя обещание извлекать API во всех современных браузерах:

function App() {
  const [msg, setMsg] = React.useState('click the button')
  const handler = () =>
    fetch('https://myapi.com/')
      .then((x) => x.json())
      .then(({ msg }) => setMsg(msg))

  return (
    

message: {msg}

) }

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

Обещания потерпеть неудачу: состояние ошибки

Обещания терпят неудачу. Это слишком легко только код для «счастливого пути», где всегда работает ваша сеть, и ваша API всегда возвращает успешный результат. Большинство разработчиков все это слишком знакомы с непоколебимыми исключениями, которые возникают только в производстве, которые заставляют ваше приложение, похоже, он не работает или не застрял в каком-то состоянии загрузки. Есть Правила Eslint, чтобы убедиться, что вы пишете .catch обработчики на ваших обещаниях.

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

В любом случае, в конечном итоге ответственность за отображение состояния ошибки упадет на вас и будет выглядеть что-то подобное:

function App() {
  const [msg, setMsg] = React.useState('click the button')
  const [err, setErr] = React.useState(null)
  const handler = () => {
    setErr(null)
    fetch('https://myapi.com/')
      .then((x) => x.json())
      .then(({ msg }) => setMsg(msg))
      .catch((err) => setErr(err))
  }

  return (
    

message: {msg}

{err &&
{err}
}
) }

Теперь у нас есть два состояния для обработки каждой асинхронной операции в нашем приложении!

Обещания в процессе: государство погрузки

При пинии ваших API на вашей местной машине ( , например, с NetLify Dev ), довольно распространено, чтобы получить быстрые ответы. Тем не менее, это игнорирует тот факт, что задержка API может быть хорошей сделки более медленнее в реальном мире, особенно на мобильном, средах. Когда кнопка нажала, обещающие пожары, однако в интерфейсе UI нет визуальной обратной связи, чтобы сообщить пользователю, что щелчок был зарегистрирован, и данные вытекают. Таким образом, пользователи часто снова нажимают, на случай, если они ошибаются, и генерируют еще больше запросов API. Это ужасный пользовательский опыт, и нет причин для записи обработчиков кликов так, кроме того, что это значение по умолчанию.

Вы можете сделать свое приложение более отзывчивым (и менее расстраивающим), предлагая некоторую форму загрузки состояния:

function App() {
  const [msg, setMsg] = React.useState('click the button')
  const [loading, setLoading] = React.useState(false)
  const handler = () => {
    setLoading(true)
    fetch('https://myapi.com/')
      .then((x) => x.json())
      .then(({ msg }) => setMsg(msg))
      .finally(() => setLoading(false))
  }

  return (
    

message: {msg}

{loading &&
loading...
}
) }

У нас сейчас есть Три Штаты для обработки каждой асинхронной операции в нашем приложении: результат, загрузка и состояние ошибки! Ой Вей.

Обещания тупые: состояние компонента

Однажды обещает уволить, они не могут быть отменены. Это был Спорное решение В то время, и хотя платформа конкретные обходные пути, такие как прерваная выборка Существуют, ясно, что мы никогда не будем отменять обещания на самом языке. Это вызывает проблемы, когда мы увольняем обещания, а затем их больше не нуждаются в том, например, когда компонент, который он должен обновлять, отключен (потому что пользователь навигациируется где-то еще).

В реакции это вызывает ошибку только в разработке:

Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.

# or

Warning: Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.

Вы можете избежать этой утечки памяти, отслеживая состояние монтажа компонента:

function App() {
  const [msg, setMsg] = React.useState('click the button')
  const isMounted = React.useRef(true)
  const handler = () => {
    setLoading(true)
    fetch('https://myapi.com/')
      .then((x) => x.json())
      .then(({ msg }) => {
        if (isMounted.current) {
          setMsg(msg)
        }
      })
  }
  React.useEffect(() => {
    return () => (isMounted.current = false)
  })

  return (
    

message: {msg}

) }

Мы использовали здесь, как Это ближе к умственной модели переменной экземпляра , но вы не заметите слишком много разницы, если вы Уместите вместо.

Давние пользователи реагирования также будут помнить, что ismounted – это антипаттерн Однако отслеживание _ismounted В качестве переменной экземпляра все еще рекомендуется, если вы не используете отмены обещания. (Что это все. Время.)

Для тех, кто подсчитывается, мы сейчас на Четыре Государствам, необходимые для отслеживания для одной операции Async в компоненте.

Решение: просто оберните его

Проблема должна быть довольно четкой сейчас:

В простой демонстрации «голые» обещает работать нормально.

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

Звучит как хорошее место для использования библиотеки, не так ли?

К счастью, довольно много существует.

React-Async ‘s seasync Крючок позволяет пройти Обещаниеfn вместе с несколькими удобными Варианты Чтобы добавить обратные вызовы и другие расширенные упреки:

import { useAsync } from 'react-async'

const loadCustomer = async ({ customerId }, { signal }) => {
  const res = await fetch(`/api/customers/${customerId}`, { signal })
  if (!res.ok) throw new Error(res)
  return res.json()
}

const MyComponent = () => {
  const { data, error, isLoading } = useAsync({ promiseFn: loadCustomer, customerId: 1 })
  if (isLoading) return 'Loading...'
  if (error) return `Something went wrong: ${error.message}`
  if (data)
    return (
      
Loaded some data:
{JSON.stringify(data, null, 2)}
) return null }

Это также включает в себя удобство usefetch Крюк, который вы можете использовать вместо родных извлекать выполнение.

Воспользуйтесь реагированием также предлагает простой seasync Реализация , где вы просто проходите в обещании (AKA Async Функция):

import { useAsync } from 'react-use'

const Demo = ({ url }) => {
  const state = useAsync(async () => {
    const response = await fetch(url)
    const result = await response.text()
    return result
  }, [url])

  return (
    
{state.loading ? (
Loading...
) : state.error ? (
Error: {state.error.message}
) : (
Value: {state.value}
)}
) }

Наконец, Daishi Kato’s React-Cooks-async также предлагает очень хорошее прервать Контроллер для любых обещаний:

import React from 'react'

import { useFetch } from 'react-hooks-async'

const UserInfo = ({ id }) => {
  const url = `https://reqres.in/api/users/${id}?delay=1`
  const { pending, error, result, abort } = useFetch(url)
  if (pending)
    return (
      
Loading...
) if (error) return (
Error: {error.name} {error.message}
) if (!result) return
No result
return
First Name: {result.data.first_name}
} const App = () => (
)

Вы также можете выбрать Используйте наблюдаемые вещи либо обернув обещание в одном или просто используя их прямо.

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

Если у вас есть дополнительные комментарии и краевые случаи, о которых я не думал, пожалуйста связаться!