2016-06-10 9 views
34

Abbiamo un'azione che recupera un oggetto asincrono, chiamiamolo getPostDetails, che accetta un parametro di cui post per recuperare un id. . L'utente viene presentato con un elenco di post e può fare clic su uno per ottenere alcuni dettagli.Come evitare le condizioni di gara durante il recupero dei dati con Redux?

Se un utente fa clic su "Post # 1", inviamo un'azione GET_POST che potrebbe essere simile a questa.

const getPostDetails = (id) => ({ 
    type: c.GET_POST_DETAILS, 
    promise: (http) => http.get(`http://example.com/posts/#${id}`), 
    returnKey: 'facebookData' 
}) 

Questo viene prelevato da un middleware, che aggiunge un gestore successo alla promessa, che chiamerà un'azione come GET_POST__OK con l'oggetto JSON deserializzato. Il riduttore vede questo oggetto e lo applica a un negozio. Un tipico riduttore __OK si presenta così.

[c.GET_ALL__OK]: (state, response) => assign(state, { 
    currentPost: response.postDetails 
}) 

tardi giù la linea abbiamo un componente che guarda currentPost e visualizza i dettagli per il post corrente.

Tuttavia, abbiamo una condizione di gara. Se un utente invia due azioni GET_POST_DETAILS l'una dopo l'altra, c'è non è garantito quale ordine riceveremo le azioni __OK in, se la seconda richiesta HTTP termina prima del primo, lo stato diventa errato.

Action     => Result 
    --------------------------------------------------------------------------------- 
|T| User Clicks Post #1  => GET_POST for #1 dispatched => Http Request #1 pending 
|i| User Clicks Post #2  => GET_POST for #2 dispatched => Http Request #2 pending 
|m| Http Request #2 Resolves => Results for #2 added to state 
|e| Http Request #1 Resolves => Results for #1 added to state 
V 

Come possiamo essere sicuri che l'ultimo elemento su cui l'utente ha fatto clic abbia sempre la priorità?

risposta

71

Il problema è dovuto a un'organizzazione di stato subottimale.

In un'applicazione Redux, le chiavi di stato come currentPost sono in genere un anti-pattern. Se è necessario "reimpostare" lo stato ogni volta che si accede a un'altra pagina, si è perso uno degli main benefits of Redux (or Flux): memorizzazione nella cache. Ad esempio, non è più possibile tornare indietro immediatamente se una navigazione ripristina lo stato e ripristina i dati.

Un modo migliore per memorizzare queste informazioni sarebbe di separare postsById e currentPostId:

{ 
    currentPostId: 1, 
    postsById: { 
    1: { ... }, 
    2: { ... }, 
    3: { ... } 
    } 
} 

Ora è possibile recuperare il maggior numero di messaggi allo stesso tempo, come ti piace, e indipendentemente unirle in cache postsById senza preoccuparsi se il post recuperato è quello attuale.

All'interno del componente, si legge sempre state.postsById[state.currentPostId] o, meglio, si esporta il selettore getCurrentPost(state) dal file di riduttore in modo che il componente non dipenda dalla forma dello stato specifica.

Ora non ci sono condizioni di gara e si dispone di una cache di post, quindi non è necessario effettuare il recupero quando si torna indietro. Successivamente, se si desidera controllare il post corrente dalla barra degli indirizzi, è possibile rimuovere completamente lo currentPostId dallo stato di Redux e leggerlo dal router; il resto della logica resterebbe lo stesso.


Anche se questo non è esattamente lo stesso, mi capita di avere un altro esempio con un problema simile. Dai un'occhiata allo code before e allo code after.Non è piuttosto uguale alla tua domanda, ma si spera che mostri come l'organizzazione statale può aiutare a evitare le condizioni di gara e gli oggetti di scena incoerenti.

Ho anche registrato una serie di video gratuiti che spiega questi argomenti, quindi è possibile che si desideri check it out.

+0

Ho una situazione leggermente diversa. Cosa devo fare se ho anche 'currentPostId' e' postById', ma devo recuperare i dati su tutti i messaggi al momento (tempo quando creato, immagine di anteprima, meta, ecc.) E quindi recuperare il contenuto di un solo post selezionato e cache questo contenuto con tutto il resto su questo post in 'postById'? – denysdovhan

+0

@DanAbramov Ciao, so che questa è una domanda piuttosto vecchia, ma poiché nessun creatore di azioni non ha accesso allo stato, come può il creatore dell'azione decidere se è stato memorizzato nella cache o ha bisogno di recuperare l'oggetto remoto? – Vinz243

+0

Cosa succede se si ha 'currentListOfSearchResults' che è un array, invece di un solo' currentPost'? Di solito finisco per saltare Redux per questi risultati e semplicemente inserirli nello stato locale per il mio componente di pagina. –

3

La soluzione di Dan è probabilmente migliore, ma una soluzione alternativa è interrompere la prima richiesta quando inizia la seconda.

Puoi farlo dividendo il tuo creatore di azioni in uno asincrono che può leggere dal negozio e inviare altre azioni, che redux-thunk ti permette di fare.

La prima cosa che il tuo creatore di azioni asincrone dovrebbe fare è controllare lo store per una promessa esistente e interromperlo se ce n'è uno. In caso contrario, può effettuare la richiesta e inviare un'azione "richiesta inizia", ​​che contiene l'oggetto promessa, che viene archiviato per la volta successiva.

In questo modo, solo la promessa creata più di recente verrà risolta. Quando lo fai, puoi inviare un'azione di successo con i dati ricevuti. Puoi anche inviare un'azione di errore se la promessa viene rifiutata per qualche motivo.

+5

http://i.kinja-img.com/gawker-media/image/upload/aoz8kgx8pzknypz7z38n.jpg –

+5

Si noti che non è consigliabile archiviare le promesse nello stato. Idealmente dovrebbe essere serializzabile. Tuttavia, descrivo l'approccio suggerito in [questa lezione] (https://egghead.io/lessons/javascript-redux-avoiding-race-conditions-with-thunks?course=building-react-applications-with- idiomatica-redux). È un buon approccio ma non sostituisce lo stato di normalizzazione. (Combinare entrambi gli approcci è una buona idea.) –