Состояние загружаемых данных

Одна из повседневных задач каждого фронтендера — загрузка данных с сервера. На время загрузки обычно требуется показывать пользователю спиннер, а в случае неудачи — сообщение об ошибке. В коде состояние загрузки часто описывают булевыми флагами:

const comments = {
  isLoading: false,
  isLoaded: false,
  isError: false,
  errorText: '',
  data: [],
};

На первый взгляд такие флаги выглядят удобно. При их использовании код загрузки данных выглядит примерно так:

const comments = {
  isLoading: false,
  isLoaded: false,
  isError: false,
  errorText: '',
  data: [],
};

function loadComments() {
  comments.isLoaded = false;
  comments.isError = false;
  comments.isLoading = true;

  fetch('/comments')
    .then((response) => response.json())
    .then((data) => {
      comments.isLoading = false;
      comments.isLoaded = true;
      comments.data = data;
    })
    .catch((error) => {
      commments.isLoading = false;
      comments.isError = true;
      comments.errorText = error.message;
    });
}

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

Более практичный подход, решающий эту проблему — описание состояния одним полем dataState и автоматическое вычисление флагов на основе значения этого поля:

const dataStates = {
  notAsked: 'notAsked',
  loading: 'loading',
  loaded: 'loaded',
  failed: 'failed',
};

const comments = {
  dataState: dataStates.notAsked,
  errorText: '',
  data: [],

  get isLoading() {
    return this.dataState === dataStates.loading;
  },

  get isLoaded() {
    return this.dataState === dataStates.loaded;
  },

  get isError() {
    return this.dataState === dataStates.failed;
  },
};

Кода стало чуть больше, зато значения всех флагов меняются автоматически, и вероятность ошибки из-за неправильного обновления одного из флагов сведена к нулю. Функция загрузки комментариев становится значительно проще:

function loadComments() {
  comments.dataState = dataStates.loading;

  fetch('/comments')
    .then((response) => response.json())
    .then((data) => {
      comments.dataState = dataStates.loaded;
      comments.data = data;
    })
    .catch((error) => {
      comments.dataState = dataStates.failed;
      comments.errorText = error.message;
    });
}