Redux: Что за идиома redux для ожидания нескольких асинхронных вызовов?

Созданный на 14 сент. 2015  ·  30Комментарии  ·  Источник: reduxjs/redux

Когда я запускаю компонент реакции, мне нужно сделать 2 разных асинхронных HTTP-вызова и дождаться их результатов.
Эти 2 вызова в идеале помещают свои данные в 2 разных редуктора.

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

Как это сделать с помощью redux?

question

Самый полезный комментарий

Я предполагаю, что вы используете промежуточное ПО Redux Thunk .

Объявите создателей действий:

import 'babel-core/polyfill'; // so I can use Promises
import fetch from 'isomorphic-fetch'; // so I can use fetch()

function doSomething() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING, json }),
      err => dispatch({ type: SOMETHING_FAILED, err })
    );
}

function doSomethingElse() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING_ELSE, json }),
      err => dispatch({ type: SOMETHING_ELSE_FAILED, err })
    );
}

Обратите внимание, как я стараюсь возвращать Promise в самом конце, сохраняя цепочку как возвращаемое значение функции внутри создателей действий преобразователя. Это позволяет мне сделать это:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

Когда вы используете Redux Thunk, dispatch возвращает возвращаемое значение функции, которую вы вернули от создателя действия преобразователя - в данном случае Promise.

Это позволяет использовать комбинаторы типа Promise.all() для ожидания завершения нескольких действий:

Promise.all([
  store.dispatch(doSomething()),
  store.dispatch(doSomethingElse())
]).then(() => {
  console.log('I did everything!');
});

Однако предпочтительнее определить другого создателя действия, который будет действовать как _composition_ из предыдущих создателей действий и будет использовать Promise.all внутри:

function doEverything() {
  return dispatch => Promise.all([
    dispatch(doSomething()),
    dispatch(doSomethingElse())
  ]);
}

Теперь ты можешь просто написать

store.dispatch(doEverything()).then(() => {
  console.log('I did everything!');
});

Удачной отправки!

Все 30 Комментарий

Я предполагаю, что вы используете промежуточное ПО Redux Thunk .

Объявите создателей действий:

import 'babel-core/polyfill'; // so I can use Promises
import fetch from 'isomorphic-fetch'; // so I can use fetch()

function doSomething() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING, json }),
      err => dispatch({ type: SOMETHING_FAILED, err })
    );
}

function doSomethingElse() {
  return dispatch => 
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING_ELSE, json }),
      err => dispatch({ type: SOMETHING_ELSE_FAILED, err })
    );
}

Обратите внимание, как я стараюсь возвращать Promise в самом конце, сохраняя цепочку как возвращаемое значение функции внутри создателей действий преобразователя. Это позволяет мне сделать это:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

Когда вы используете Redux Thunk, dispatch возвращает возвращаемое значение функции, которую вы вернули от создателя действия преобразователя - в данном случае Promise.

Это позволяет использовать комбинаторы типа Promise.all() для ожидания завершения нескольких действий:

Promise.all([
  store.dispatch(doSomething()),
  store.dispatch(doSomethingElse())
]).then(() => {
  console.log('I did everything!');
});

Однако предпочтительнее определить другого создателя действия, который будет действовать как _composition_ из предыдущих создателей действий и будет использовать Promise.all внутри:

function doEverything() {
  return dispatch => Promise.all([
    dispatch(doSomething()),
    dispatch(doSomethingElse())
  ]);
}

Теперь ты можешь просто написать

store.dispatch(doEverything()).then(() => {
  console.log('I did everything!');
});

Удачной отправки!

@gaearon Есть ли какая-то конкретная причина, по которой вы предпочитаете thunk promise промежуточному программному обеспечению

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

Разобрался, спасибо!

JFYI :)
screen shot 2015-09-14 at 11 06 42 am

если хотите, можете взглянуть на него https://github.com/Cron-J/redux-async-transitions

@gaearon быстрый вопрос - глядя на ваш пример выше, где вы звоните:

store.dispatch(doSomething()).then(() => {
  console.log('I did something');
});

- похоже, что блок «Я что-то сделал» будет сработал, даже если doSomething отклонен - ​​поскольку вы не повторно генерируете ошибку в цепочке doSomething после того, как поймаете ее. Это намеренно? Если нет - и если бы вы вместо этого повторно выбросили ошибку - вы бы обработали ее снова в вызывающей программе?

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

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

Хорошо, спасибо! Подумал, что это так, но хотел убедиться, что нет какой-то магии, которую я упускал.

Поскольку Redux настраивается, возвращаемое значение dispatch не является ожидаемым обещанием.

На самом деле, я столкнулся с одной проблемой в реальной жизни, поскольку я использую redux-loop. Он настраивает dispatch для возврата обернутого обещания (https://github.com/raisemarketplace/redux-loop/blob/master/modules/effects.js#L38). Итак, мы не можем зависеть от возвращаемого значения dispatch .

Что ж, как Дэн сказал выше в этой ветке: это ваше приложение, вы пишете свои создатели действий, поэтому вы сами решаете, что они возвращают.

@markerikson Не dispatch будет возвращаемым значением создателя действия, поскольку промежуточное ПО может его настроить.

Но у меня есть обходной путь. Вместо цепочки then на dispatch(actonCreator()) , мы можем цеплять на actionCreator() .

Да, промежуточное ПО может настраивать вещи, но я хочу сказать, что _вы_ настраиваете промежуточное ПО в своем собственном приложении :)

Привет @gaearon , быстрый (надеюсь, связанный) вопрос! Каковы ваши взгляды на что-то вроде

function fetchDataBWhichDependsOnA() {
  return dispatch => {
    dispatch(fetchDataA())
      .then(() => {
        fetch('/api/get/data/B')
          .then(dispatch(receiveDataB(response.data)))
      })
  }
}

В этом фрагменте кода есть 2 вложенных асинхронных действия, они работают, но у меня проблемы с тестированием. Я могу протестировать fetchDataA() потому что он имеет только 1 уровень асинхронного вызова. Но когда я пытался написать тест для fetchDataBWhichDependsOnA() , я не мог получить самые обновленные значения из блока store в блоке then .

@timothytong Вы не возвращаете обещания. Если вы вернете результат из dispatch и вложенного вызова fetch вы сможете подождать в своих тестах, пока вся цепочка не будет разрешена.

@johanneslumpe хороший улов, спасибо,

У меня была та же проблема, и я потратил 30 минут, пытаясь придумать решение. Я еще не закончил эту реализацию, но чувствую, что она на правильном пути.

https://github.com/Sailias/sync-thunk

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

@gaearon

Promise.all([
  store.dispatch(doSomething()),
  store.dispatch(doSomethingElse())
]).then(() => {
  console.log('I did everything!');
});

У меня есть вопрос по этому поводу. Извините, если это так просто.

В этом сценарии будет две отправки. Это означает, что компонент будет визуализирован дважды. Но что, если компонент хочет, чтобы оба данных показывали что-то значимое?

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

Я получил названия нескольких промежуточных программ, которые, похоже, решают эту проблему, например, redux-batched-subscribe и redux-batched-actions . Но немного запутались в лучшем подходе.

Если это допустимый сценарий, каков будет предложенный подход к этому?

@jintoppy, если вы просите избежать многократного вызова для рендеринга компонента, тогда решение Promise.all вам не поможет; потому что оба doSomething() and doSomething() все данные отправки, которые вызовут изменение состояния и вызовут рендеринг компонента.

@jintoppy, зачем вам какое-либо промежуточное программное обеспечение, чтобы справиться с вашей ситуацией?
Что вам мешает делать что-л.

// action creator
function fetchAB() {
  return dispatch => {
    const fetchA = fetch( 'api/endpoint/A' );
    const fetchB = fetch( 'api/endpoint/B' );
    return Promise.all([ fetchA, fetchB ])
      .then( values => dispatch(showABAction(values)) )
      .catch( err => throw err );
  }
} 

Вы можете просто поместить весь свой запрос в создателя одного действия и запустить отправку действия только после того, как все они будут решены. В этом случае у вас вызван reducer и, таким образом, состояние изменилось только один раз, поэтому render (), вероятно, также будет запущен только один раз.

@apranovich : Я вижу в этом подходе одну проблему. Теперь мне нужно объединить несколько выборок в одном действии. Итак, если у меня есть, скажем, 10 асинхронных вызовов, я должен объединить все их в одно действие. Кроме того, что, если я захочу позже запустить один из этих 10 асинхронных вызовов? В этом подходе, по сути, вся ваша асинхронная логика будет заключаться в одном действии.

Спасибо всем за полезную информацию. Быстрый вопрос, как можно добиться того же, но вместо этого, когда одна выборка возвращает данные, которые требуются для следующего метода выборки. Например, есть бесплатная конечная точка API погоды, где я ввожу геолокацию, чтобы получить идентификатор (сначала fetch ), который требуется для следующего fetch чтобы получить погоду для местоположения. Я знаю, что это странный вариант использования, но я хотел бы знать, как справляться с подобными ситуациями. Заранее спасибо!

Привет, @gaearon , я хотел бы получить ваш отзыв по этому моему вопросу. Мне нужно выполнить вложенные асинхронные вызовы двух API. Ответ второго API зависит от ответа первого.

export const getApi=()=>{
        return(d)=>{
        axios({
            method:'GET',
            url:Constants.URLConst+"/UserProfile",
            headers:Constants.headers
        }).then((response)=>{
            return d({
                type:GET_API_DATA,
                response,
                axios({
                    method:'GET',
                    url:Constants.URLConst+"/UserProfileImage?enterpriseId="+response.data.ProfileData.EnterpriseId,
                    headers:Constants.headers
                }).then((response1)=>{
                    return d({
                        type:GET_PROFILE_IMAGE,
                        response1
                    })
                })
            })
        }).catch((e)=>{
            console.log("e",e);
        })
    }

}

Я пробовал что-то подобное, но это не работает. И в моем проекте я должен использовать ответ от обоих API независимо.
Итак, как правильно это делать?

Благодарю.

@ c0b41 , проверьте обновленный вопрос.

эй @ c0b41 , это дает синтаксические ошибки во втором вызове axios.

@ aayushis12 , @ c0b41 : это трекер ошибок, а не справочный форум. Вам следует попробовать задать этот вопрос на Stack Overflow или Reactiflux. Будет больше людей, которые увидят вопрос и смогут помочь.

Для тех, кто испытывает это

Я все сделала

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

Например, если doSomethingElse() объявлен с {} в стрелочной функции

function doSomethingElse() {
  return dispatch => {
    fetch(
      '/api/something'
    ).then(
      response => response.json()
    ).then(
      json => dispatch({ type: DO_SOMETHING_ELSE, json }),
      err => dispatch({ type: SOMETHING_ELSE_FAILED, err })
    );
  }
}

«Я сделал все» будет напечатано до разрешения doSomethingElse() . Решите эту проблему, отбросив {} после => .

@gaearon один вопрос, основанный на https://github.com/reduxjs/redux/issues/723#issuecomment -139927639, как бы вы справились с этим, если вам нужно отправить действие, которое ожидает отправки двух действий _AND_ состояние изменились из-за этих действий? У меня случай, когда действия отправляются, но действие внутри then выполняется _до_ изменения состояния. Мы используем redux-thunk .

@enmanuelduran : Дэн больше не является активным сопровождающим - пожалуйста, не пингуйте его.

Трудно ответить на ваш вопрос, не видя кода, но наши проблемы не предназначены для обсуждения на форуме. Было бы лучше, если вы разместите свой вопрос на Stack Overflow - вы, вероятно, получите лучший ответ там быстрее.

@markerikson ой, извините, это не было моим намерением, создал вопрос в stackoverflow, если кому-то интересно: https://stackoverflow.com/questions/51846612/dispatch-an-action-after-other-specific-actions-were- отправлено-и-изменение состояния

спасибо большое, ребята.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги