Skip to content

Instantly share code, notes, and snippets.

@alexeyraspopov
Created February 10, 2021 22:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexeyraspopov/806b92903a66d9579ddaa762e45f751d to your computer and use it in GitHub Desktop.
Save alexeyraspopov/806b92903a66d9579ddaa762e45f751d to your computer and use it in GitHub Desktop.

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

Race Condition

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

function App() {
  let [query, setQuery] = useState(null);
  let [data, setData] = useState(null);

  useEffect(() => {
    fetchData(query).then((data) => setData(data));
  }, [query]);

  return (
    <Fragment>
      <RadioGroup onChange={(value) => setQuery(value)}>
        <RadioOption label="Version A" value="A" />
        <RadioOption label="Version B" value="B" />
        <RadioOption label="Version C" value="C" />
      </RadioGroup>

      {data != null ? <DataTable data={data} /> : null}
    </Fragment>
  );
}

Последовательно нажимаем первую, вторую, третью вкладки, видим как появляются новые данные в таблице. Но что если сервер на самом деле выглядит вот так:

function fetchData(query) {
  return new Promise((resolve) => {
    let data = [];
    // simulating latency
    switch (query) {
      case 'A':
        setTimeout(resolve, 5 * 1000, data);
        break;
      case 'B':
        setTimeout(resolve, 2 * 1000, data);
        break;
      case 'C':
        setTimeout(resolve, 2 * 1000, data);
        break;
    }
  });
}

Красота функциональных компонентов и хуков состоит в том что в коде нет понятия времени, всё декларативно. Если код легко читать, скорее всего он не может быть сильно поломан, иначе это было бы видно. Но промисы в джаваскрипте не такие, они пронзают весь пространственно-временной континуум. Чтобы понять потенциальный баг в примере выше, нужно представить что происходит с памятью когда UI делает новые запросы

time ->
fetch query A -------------------------
fetch query B ----------
fetch query C ----------

Если мы поочерёдно нажмем все три вкладки, в итоге мы увидим результат нажатия последней вкладки, но это будет ненадолго. Самый первый запрос в примере занимает чуть больше времени и к моменту когда он закончится его результаты нам уже не нужны. Но так уже получилось что промис остался в памяти, и в его скоупе оказался доступ к setData, всё ещё валидный. Этот промис резолвится, setData вызывается с результатом первого запроса, интерфейс начинает показывать данные таблицы A независимо от того какая таба открыта на текущий момент.

Orphaned Suspense

Предположим, ситуация с race condition решилась через саспенс.

function App() {
  let [query, setQuery] = useState(null);
  let resource$ = useResource(DataResource, [query]);

  return (
    <Fragment>
      <RadioGroup onChange={(value) => setQuery(value)}>
        <RadioOption label="Version A" value="A" />
        <RadioOption label="Version B" value="B" />
        <RadioOption label="Version C" value="C" />
      </RadioGroup>

      <Suspense fallback={<Spinner />}>
        <DataTable resource$={resource$} />
      </Suspense>
    </Fragment>
  );
}

Но предположим что проблема с сервером всё та же, первый запрос будет занимать критически больше времени чем другие. Когда Саспенс вылавливает промис, скоуп этого элемента просто делает promise.then(render). В примере это будет промис таблицы А, которая занимает очень много времени. Если мы начнём переключаться между вкладками во время того как появился Саспенс для первой таблицы, нам всё равно придётся дождаться резолва этого первого промиса, потому что рендер который приведёт к новому Саспенсу ещё не может случиться.

Всех бы этих проблем не было, если бы у промисов был нормальный механим отмены.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment