Redux: qual é o idioma redux para esperar por várias chamadas assíncronas?

Criado em 14 set. 2015  ·  30Comentários  ·  Fonte: reduxjs/redux

Quando eu inicio o componente de reação, preciso fazer 2 chamadas http assíncronas diferentes e aguardar os resultados.
Idealmente, essas 2 chamadas colocarão seus dados em 2 redutores diferentes.

Eu entendo o padrão para fazer uma única chamada assíncrona.
No entanto, não vejo nenhum bom exemplo para fazer várias chamadas assíncronas, fazendo-as executar em paralelo e aguardar _both_ seus resultados chegarem.

Qual é a maneira redux de fazer isso?

question

Comentários muito úteis

Presumo que você use o middleware Redux Thunk .

Declare alguns criadores de ação:

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 })
    );
}

Observe como faço questão de retornar Promise no final, mantendo a cadeia como valor de retorno da função dentro dos criadores de ação thunk. Isso me permite fazer isso:

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

Quando você usa Redux Thunk, dispatch retorna o valor de retorno da função que você retornou do criador da ação thunk - neste caso, a Promessa.

Isso permite que você use combinadores como Promise.all() para aguardar a conclusão de várias ações:

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

No entanto, é preferível definir outro criador de ação que atuaria como uma _composição_ dos criadores de ação anteriores e usar Promise.all internamente:

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

Agora você pode apenas escrever

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

Feliz despacho!

Todos 30 comentários

Presumo que você use o middleware Redux Thunk .

Declare alguns criadores de ação:

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 })
    );
}

Observe como faço questão de retornar Promise no final, mantendo a cadeia como valor de retorno da função dentro dos criadores de ação thunk. Isso me permite fazer isso:

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

Quando você usa Redux Thunk, dispatch retorna o valor de retorno da função que você retornou do criador da ação thunk - neste caso, a Promessa.

Isso permite que você use combinadores como Promise.all() para aguardar a conclusão de várias ações:

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

No entanto, é preferível definir outro criador de ação que atuaria como uma _composição_ dos criadores de ação anteriores e usar Promise.all internamente:

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

Agora você pode apenas escrever

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

Feliz despacho!

@gaearon Existe algum motivo específico para você preferir thunk vez de promise middleware? ou conselhos sobre quando usar o quê? Obrigado.

Nenhuma razão particular, apenas estou mais familiarizado com ele.
Experimente os dois e use o que funciona melhor para você!

Compreendido, obrigado!

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

se você quiser, pode dar uma olhada nele https://github.com/Cron-J/redux-async-transitions

@gaearon pergunta rápida - olhando para o seu exemplo acima, onde você chama:

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

- parece que o bloco "Eu fiz algo" seria atingido mesmo se doSomething fosse rejeitado - já que você não está relançando o erro na cadeia doSomething depois de capturá-lo. Isso é intencional? Se não - e se você relançasse o erro lá em vez disso - você trataria de novo no chamador?

É apenas um exemplo, não quero dizer que seja uma referência definitiva, apenas uma forma de ver. Eu não pensei muito nisso.

Você sabe como devolver as promessas dos criadores de ação, o resto é com você. Se você deve relançar os erros e onde tratá-los, depende de você. Isso não é realmente uma preocupação do Redux, então meu conselho é tão bom quanto o de qualquer outra pessoa e, neste caso, você tem muito mais contexto sobre o que deseja construir e o estilo que prefere do que eu jamais terei.

Legal, obrigado! Percebi que era o caso, mas queria ter certeza de que não havia nenhuma magia que eu estava esquecendo.

Como Redux é personalizável, o valor de retorno de dispatch não é uma promessa esperada.

Na verdade, encontrei um problema na vida real, uma vez que faço o redux-loop. Ele ajusta dispatch para retornar uma promessa embrulhada (https://github.com/raisemarketplace/redux-loop/blob/master/modules/effects.js#L38). Portanto, não podemos depender do valor de retorno de dispatch .

Bem, como Dan disse acima neste tópico: é _seu_ aplicativo, _você_ está escrevendo seus criadores de ação, então _você_ pode decidir o que eles retornam.

@markerikson O valor de retorno de dispatch não é garantido como o valor de retorno do criador da ação, já que o middleware pode ajustá-lo.

Mas, eu tenho uma solução alternativa. Em vez da corrente then em dispatch(actonCreator()) , podemos acorrentar actionCreator() .

Sim, o middleware pode ajustar as coisas, mas meu ponto é que _você_ está configurando o middleware em seu próprio aplicativo :)

Olá @gaearon ,

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

Existem 2 ações assíncronas aninhadas neste trecho de código, elas funcionam, mas estou tendo problemas para testá-las. Posso testar fetchDataA() porque ele tem apenas 1 nível de chamada assíncrona. Mas quando estava tentando escrever um teste para fetchDataBWhichDependsOnA() , não consegui recuperar os valores mais atualizados do bloco store em then .

@timothytong Você não está devolvendo as promessas. Se você retornar o resultado de dispatch e a chamada aninhada fetch você poderá esperar em seus testes até que toda a cadeia seja resolvida.

@johanneslumpe boa captura, obrigado companheiro! Agora tudo o que resta é a questão de saber se isso é aceitável ou não, lidar com a dependência de dados com uma cadeia de busca assíncrona.

Tive o mesmo problema e passei 30 minutos tentando encontrar uma solução. Ainda não terminei esta implementação, mas sinto que está no caminho certo.

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

Minha ideia é que um componente pode solicitar que certos estados redux sejam preenchidos; se não forem, deve se referir a um mapa para chamar as ações para preenchê-los e encadea-los com then . Cada ação na cadeia não precisa de dados passados, ela pode apenas pegar os dados do estado conforme as ações dependentes os teriam preenchido.

@gaearon

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

Eu tenho uma pergunta sobre isso. Desculpe se é tão básico.

Nesse cenário, haverá dois despachos. Isso significa que duas vezes o componente será renderizado. Mas, e se o componente quiser que os dois dados mostrem algo significativo?

Portanto, nesse cenário, gostaria que o componente renderizasse apenas uma vez, quando todas as ações assíncronas fossem concluídas.

Eu tenho nomes de alguns middlewares que parecem estar lidando com esse problema, como redux-batched-subscribe e redux-batched-actions . Mas, um pouco confuso sobre a melhor abordagem.

Se este for um cenário válido, qual seria a abordagem sugerida para lidar com isso?

@jintoppy se sua solicitação for evitar várias chamadas para renderizar um componente, a solução Promise.all não o ajudará; porque doSomething() and doSomething() todos os dados de despacho que desencadeariam a mudança de estado e causariam a renderização do componente.

@jintoppy por que você precisa de algum middlware para lidar com sua situação?
O que o impede de fazer algo assim.

// 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 );
  }
} 

Você pode simplesmente colocar todas as suas solicitações no criador de uma ação e disparar o despacho da ação apenas quando todas elas forem resolvidas. Nesse caso, você chamou o redutor e, portanto, o estado foi alterado apenas uma vez, então render () provavelmente será disparado apenas uma vez também.

@apranovich : Vejo um problema nessa abordagem. Agora, preciso combinar vários fetch em uma única ação. Então, se eu tiver, digamos 10 chamadas assíncronas, terei que colocar todas elas em uma única ação. Além disso, e se eu quiser acionar uma dessas 10 chamadas assíncronas posteriormente? Nesta abordagem, basicamente, toda a sua lógica assíncrona residirá em uma única ação.

Obrigado a todos pela informação útil. Uma pergunta rápida, como o mesmo pode ser alcançado, mas em vez disso, quando uma busca retorna dados que são exigidos pelo próximo método de busca. Por exemplo, há um endpoint de API de clima gratuito onde eu insiro a geolocalização para obter um id (primeiro fetch ) que é necessário para o próximo fetch para obter o clima do local. Eu sei que este é um caso de uso estranho, mas gostaria de saber como lidar com situações semelhantes. Desde já, obrigado!

Olá @gaearon , gostaria de

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);
        })
    }

}

Tentei algo como acima, mas não funciona. E no meu projeto, tenho que usar a resposta de ambas as APIs independentemente.
Então, qual é a maneira certa de fazer isso?

Obrigado.

@ c0b41 , verifique a pergunta atualizada.

hey @ c0b41 , isso está dando erros de sintaxe na segunda chamada de axios.

@ aayushis12 , @ c0b41 : este é um rastreador de bug, não um fórum de ajuda. Você deve tentar fazer esta pergunta no Stack Overflow ou Reactiflux. Haverá mais pessoas que verão a pergunta e poderão ajudar.

Para aqueles que experimentam isso

eu fiz tudo

é impresso antes que todas as chamadas assíncronas sejam resolvidas, verifique se você está retornando uma instrução ou uma expressão de seus criadores de ação assíncrona.

Por exemplo, se doSomethingElse() for declarado com {} na função de seta

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 })
    );
  }
}

"Eu fiz tudo" será impresso antes de doSomethingElse() resolver. Resolva isso descartando {} após => .

@gaearon one question, com base em https://github.com/reduxjs/redux/issues/723#issuecomment -139927639 como você lidaria com isso se precisasse despachar uma ação que espera por duas ações serem despachadas _E_ o estado ter mudado por causa dessas ações? Estou tendo um caso em que as ações são despachadas, mas a ação dentro de then é executada _antes_ do estado ter mudado. Estamos usando redux-thunk .

@enmanuelduran : Dan não é mais um mantenedor ativo - por favor, não faça ping nele.

É difícil responder à sua pergunta sem ver o código, mas nossos problemas realmente não se destinam a ser um fórum de discussão. Seria melhor se você postar sua pergunta no Stack Overflow - você provavelmente obterá uma resposta melhor lá, mais rápido.

@markerikson oh, desculpe, não era minha intenção, criei uma pergunta no stackoverflow se alguém estiver interessado: https://stackoverflow.com/questions/51846612/dispatch-an-action-after-other-specific-actions-were- despachado e mudança de estado

muito obrigado pessoal.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

jbri7357 picture jbri7357  ·  3Comentários

caojinli picture caojinli  ·  3Comentários

jimbolla picture jimbolla  ·  3Comentários

dmitry-zaets picture dmitry-zaets  ·  3Comentários

vslinko picture vslinko  ·  3Comentários