На данный момент мне удалось обойтись с базовой реализацией саг, но мое приложение становится немного сложнее. Я выбрал саги для асинхронных возможностей, но, похоже, неправильно понял, как все работает.

У меня есть глобальный поисковый ввод в моем приложении, которому нужно сделать два разных вызова API (разные объекты данных), но поисковый ввод также имеет свои собственные состояния загрузки, основанные на поиске / статусе API-вызовов. Основываясь на этой информации, это поток приложения:

  1. Поиск происходит (отправляет действие GLOBAL_SEARCH_REQUEST)
  2. Наблюдатель за сагами для GLOBAL_SEARCH_REQUEST запускается (устанавливает загрузку в true для ввода)
  3. В этой саге - позвоните, чтобы получить всех пользователей / подписки, которые соответствуют поисковому запросу.
  4. В случае успеха установите загрузку для входа в false
  5. При неудаче установить ошибку

Сага о глобальном поисковом запросе

function* globalSearchRequestSaga(action) {
  const { query } = action
  console.log(`searching subscriptions and users for : ${query}`)
  try {
    yield put(fetchUsersRequest(query))
    // call for the subscriptions (leaving it out for simplicity in this example)
    yield put(globalSearchSuccess(query))
  } catch (error) {
    console.log(`error: ${error}`)
    yield put(globalSearchFailure(error.message))
  }
}

Где сага пользователей выборки выглядит

export function* fetchUsersRequestSaga(action) {
  const { query } = action
  const path = `${root}/users`
  try {
    const users = yield axios.get(path, { crossDomain: true })
    yield put(fetchUsersSuccess(query, users.data))
  } catch (error) {
    console.log(`error : ${error}`)
    yield put(fetchUsersFailure(query, error.message))
  }
}

(очень простой)

Если я поступаю таким образом, возникает проблема, когда действие GLOBAL_SEARCH_SUCCESS выполняется до завершения запроса пользователей (и я представляю то же самое, если я добавляю также и в вызов подписки api). Одно решение, которое я нашел, это если я поменяю линию

yield put(fetchUsersRequest(query))

К

yield call(fetchUsersRequestSaga, fetchUsersRequest(query))

Где fetchUsersRequestSaga - сага сверху, а fetchUsersRequest(query) - создатель действий для извлечения пользователей. Это приводит к тому, asnyc функциональность работы , и {{ X2 }} ждет возвращения пользователей ( правильное поведение ) .

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

Мне интересно, есть ли способ заставить это правильно войти в магазин или вернуться к моей предыдущей реализации с правильной блокировкой put(fetchUsersRequest(query))

2
erp 18 Ноя 2019 в 23:08
Вы можете получить take при успешной или неудачной выборке пользователя, но поскольку вы поставили несколько пользовательских действий выборки, вы должны дождаться точного успеха или неудачи, которые вы только что запустили, а это означает, что вы присваиваете своему действию выборки идентификатор, а для успешного и неудачного использования этого идентификатора.
 – 
HMR
18 Ноя 2019 в 23:19
Итак, вы говорите внутри fetchUsersRequestSaga do yield take(fetchUsersSuccess(query, users.data), и это заблокировало бы выполнение globalSearchSuccess() в другой саге?
 – 
erp
18 Ноя 2019 в 23:24
Я добавил ответ, дайте мне знать, сработало ли это. Некоторое время назад сделал что-то подобное в общем слое api, поэтому, возможно, пропустил некоторую доходность (возможно, waitFor нужно return yield waitFor(id), когда он рекурсивно вызывает себя.
 – 
HMR
18 Ноя 2019 в 23:39

2 ответа

Функция put является неблокирующим действием. Он не будет ждать, пока не будет решен запрос обещания / API.

Я бы посоветовал вам просто вызывать саги напрямую, а не отправлять действия.

try {
   yield call(fetchUsersRequestSaga, query);
   yield call(globalSearchSaga, query); // or whatever its called
}

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

2
kind user 18 Ноя 2019 в 23:19
OP уже пробовал вызов, но затем пользователь выборки не отправляется, и редуктор для пользователя выборки никогда не вызывается.
 – 
HMR
18 Ноя 2019 в 23:19
Тогда почему бы не переместить «получение пользовательской логики» внутрь саги? Или просто отправьте его в сагу, но отключите globalSearchRequestSaga от прослушивания этого действия.
 – 
kind user
18 Ноя 2019 в 23:21
Откуда бы это было вызвано?
 – 
erp
18 Ноя 2019 в 23:29
Ага! Потому что есть две саги, с которыми я имею дело: пользователи и поиск, и вы предлагаете вызвать их обоих внутри этой попытки (когда я подумал, что буду звонить им из searchSaga)
 – 
erp
18 Ноя 2019 в 23:35
Вы должны вызвать их внутри globalSearchRequestSaga, как вы это делали выше. Но позвоните, а не начните действовать.
 – 
kind user
18 Ноя 2019 в 23:37

Я давно работал с sagas, но вот некоторый код, который даст вам общее представление о том, как ждать отправленного действия.

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

Если вы не хотите или вам нужно ждать, тогда вы можете просто отправить действие без идентификатора, и оно все равно будет работать:

const addId = (id => fn => (...args) => ({
  ...fn(...args),
  id: id++,
}))(0);
const withId = ({ id }, action) => ({ action, id });

function* waitFor(id) {
  const action = yield take('*');
  if (action.id === id) {
    return action;
  }
  return waitFor(id);
}

function* globalSearchRequestSaga(action) {
  const { query } = action;
  console.log(
    `searching subscriptions and users for : ${query}`
  );
  try {
    //add id to action (id is unique)
    const action = addId(fetchUsersRequest, query);
    //dispatch the action and then wait for resulting action
    //  with the same id
    yield put(action);
    const result = yield waitFor(action.id);
    // call for the subscriptions (leaving it out for simplicity in this example)
    yield put(globalSearchSuccess(query));
  } catch (error) {
    console.log(`error: ${error}`);
    yield put(globalSearchFailure(error.message));
  }
}

export function* fetchUsersRequestSaga(action) {
  const { query } = action;
  const path = `${root}/users`;
  try {
    const users = yield axios.get(path, {
      crossDomain: true,
    });
    yield put(//add original id to success action
      withId(action, fetchUsersSuccess(query, users.data))
    );
  } catch (error) {
    console.log(`error : ${error}`);
    yield put(
      withId(//add original id to fail action
        action,
        fetchUsersFailure(query, error.message)
      )
    );
  }
}
0
HMR 19 Ноя 2019 в 09:52
Спасибо за ответ. Мне понадобится время, чтобы осмотреть это
 – 
erp
18 Ноя 2019 в 23:42
Я отредактировал ответ, думаю, это был yield all([put(action),call(waitFor(action.id))]);, если вы хотите отправить действие выборки и дождаться другого действия с тем же идентификатором (успех или неудача). Может быть, yield put(action); yied call(waitFor(action.id))]); тоже подойдет, но я не уверен.
 – 
HMR
18 Ноя 2019 в 23:50
1
Я думаю, что это может быть немного сложнее для этой проблемы
 – 
kind user
19 Ноя 2019 в 00:11
Посмотрев на старый код и обновив ответ, вы можете отправить действие выборки, а затем просто const result yield waitFor(id of the action just dispatched), результатом будет либо успешное, либо неудачное действие, которое отправит fetch saga.
 – 
HMR
19 Ноя 2019 в 09:56