Redux: Wie verkettet man asynchrone Aktionen?

Erstellt am 28. Apr. 2016  ·  24Kommentare  ·  Quelle: reduxjs/redux

Hallo, ich studiere Redux und stehe vor einem interessanten Problem? müssen eine Kette von asynchronen Anforderungen von anderen Aktionen erstellen
1-getUser()
2-getPost()

Ich habe 2 Lösungen, die nach der Anmeldung des Benutzers ausgeführt werden. dann (dispatch ({type: GET_POST_REQUEST}))
oder Schreibfunktion in MiddleWare.

Wie geht es richtig?

docs

Hilfreichster Kommentar

Hallo! Dies ist ein Issue-Tracker und kein Support-Forum. Wir würden uns freuen, wenn Sie das nächste Mal auf StackOverflow fragen, da die Antworten hier im Gegensatz zu SO verloren gehen.

Wenn Sie Ihren Shop jedoch mit Redux Thunk -Middleware erstellen, können Sie asynchrone Aktionsersteller wie folgt schreiben:

// If you use Redux Thunk...
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))

// You can define asynchronous action creators that return functions.
// We call such action creators "thunks":

export function getUser(id) {
  // Redux Thunk will inject dispatch here:
  return dispatch => {
    // Reducers may handle this to set a flag like isFetching
    dispatch({ type: 'GET_USER_REQUEST', id })

    // Perform the actual API call
    return fetchUser().then(
      response => {
        // Reducers may handle this to show the data and reset isFetching
        dispatch({ type: 'GET_USER_SUCCESS', id,  response })
      },
      error => {
        // Reducers may handle this to reset isFetching
        dispatch({ type: 'GET_USER_FAILURE', id,  error })
        // Rethrow so returned Promise is rejected
        throw error
      }
    )
  }
}

// Thunks can be dispatched, if Redux Thunk is applied,
// just like normal action creators:
store.dispatch(getUser(42));

// The return value of dispatch() when you dispatch a thunk *is*
// the return value of the inner function. This is why it's useful
// to return a Promise (even though it is not strictly necessary):
store.dispatch(getUser(42)).then(() =>
  console.log('Fetched user and updated UI!')
)

// Here is another thunk action creator.
// It works exactly the same way.
export function getPost(id) {
  return dispatch => {
    dispatch({ type: 'GET_POST_REQUEST', id })
    return fetchPost().then(
      response => dispatch({ type: 'GET_POST_SUCCESS', id,  response }),
      error => {
        dispatch({ type: 'GET_POST_FAILURE', id,  error })
        throw error
      }
    )
  }
}

// Now we can combine them
export function getUserAndTheirFirstPost(userId) {
  // Again, Redux Thunk will inject dispatch here.
  // It also injects a second argument called getState() that lets us read the current state.
  return (dispatch, getState) => {
    // Remember I told you dispatch() can now handle thunks?
    return dispatch(getUser(userId)).then(() => {
      // Assuming this is where the fetched user got stored
      const fetchedUser = getState().usersById[userId]
      // Assuming it has a "postIDs" field:
      const firstPostID = fetchedUser.postIDs[0]
      // And we can dispatch() another thunk now!
      return dispatch(getPost(firstPostID))
    })
  }
}

// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
  console.log('fetched a user and their first post')
})

// We can do this anywhere we have access to dispatch().
// For example, we can use this.props.dispatch, or put action
// creators right into the props by passing them to connect, like this:
// export default connect(mapStateToProps, { getUserAndTheirFirstPost })

Wir sollten dies in die FAQ aufnehmen.

Alle 24 Kommentare

Hallo! Dies ist ein Issue-Tracker und kein Support-Forum. Wir würden uns freuen, wenn Sie das nächste Mal auf StackOverflow fragen, da die Antworten hier im Gegensatz zu SO verloren gehen.

Wenn Sie Ihren Shop jedoch mit Redux Thunk -Middleware erstellen, können Sie asynchrone Aktionsersteller wie folgt schreiben:

// If you use Redux Thunk...
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))

// You can define asynchronous action creators that return functions.
// We call such action creators "thunks":

export function getUser(id) {
  // Redux Thunk will inject dispatch here:
  return dispatch => {
    // Reducers may handle this to set a flag like isFetching
    dispatch({ type: 'GET_USER_REQUEST', id })

    // Perform the actual API call
    return fetchUser().then(
      response => {
        // Reducers may handle this to show the data and reset isFetching
        dispatch({ type: 'GET_USER_SUCCESS', id,  response })
      },
      error => {
        // Reducers may handle this to reset isFetching
        dispatch({ type: 'GET_USER_FAILURE', id,  error })
        // Rethrow so returned Promise is rejected
        throw error
      }
    )
  }
}

// Thunks can be dispatched, if Redux Thunk is applied,
// just like normal action creators:
store.dispatch(getUser(42));

// The return value of dispatch() when you dispatch a thunk *is*
// the return value of the inner function. This is why it's useful
// to return a Promise (even though it is not strictly necessary):
store.dispatch(getUser(42)).then(() =>
  console.log('Fetched user and updated UI!')
)

// Here is another thunk action creator.
// It works exactly the same way.
export function getPost(id) {
  return dispatch => {
    dispatch({ type: 'GET_POST_REQUEST', id })
    return fetchPost().then(
      response => dispatch({ type: 'GET_POST_SUCCESS', id,  response }),
      error => {
        dispatch({ type: 'GET_POST_FAILURE', id,  error })
        throw error
      }
    )
  }
}

// Now we can combine them
export function getUserAndTheirFirstPost(userId) {
  // Again, Redux Thunk will inject dispatch here.
  // It also injects a second argument called getState() that lets us read the current state.
  return (dispatch, getState) => {
    // Remember I told you dispatch() can now handle thunks?
    return dispatch(getUser(userId)).then(() => {
      // Assuming this is where the fetched user got stored
      const fetchedUser = getState().usersById[userId]
      // Assuming it has a "postIDs" field:
      const firstPostID = fetchedUser.postIDs[0]
      // And we can dispatch() another thunk now!
      return dispatch(getPost(firstPostID))
    })
  }
}

// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
  console.log('fetched a user and their first post')
})

// We can do this anywhere we have access to dispatch().
// For example, we can use this.props.dispatch, or put action
// creators right into the props by passing them to connect, like this:
// export default connect(mapStateToProps, { getUserAndTheirFirstPost })

Wir sollten dies in die FAQ aufnehmen.

Ich habe dieses Problem wie folgt gelöst. Ohne Änderungsaktionen. Ich habe die Komponente Promise eingefügt.

  clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch
      return response;
    })
    .then(function(response){console.log("@RESPONSE",response);data.show(data)})
  }

Dies ist die richtige Entscheidung?

Das funktioniert auch, welches Muster Sie verwenden, bleibt Ihnen überlassen.

@ar53n Das Muster mit einem Versprechen in einer React-Komponente hat mehrere Fehler:

  • Es gibt keine Fehlerbehandlung (im obigen Beispiel ), dh den catch -Teil. Sie können unbehandelte Ablehnungen erhalten.
  • Es ist unterbrechungsfrei, z. B. wenn die Komponente ausgehängt wird oder eine Aktion stattfindet, die den App-Status ändert.
  • Sein Zustand ist implizit. Obwohl die Nebenwirkungen eine separate Diskussion sind, wäre es zumindest nützlich, eine Spur dieses laufenden Prozesses im App-Status zu haben.

@sompylasar danke John danke für deine Kommentare. Ich möchte nur keine einfachen Aktionen ändern. Wir haben 2 einfache Aktionen Authentication und GetEvents es sind 2 asynchrone Anfragen und enthalten sogar catch . Rufen Sie einfach diese Aktion auf, wenn Sie auf die Komponente klicken
z.B

export function userAuth(login, password) {
  return (dispatch, getState) => {
    console.log('STATE', getState())
    let newState = dispatch(requestUserAuth(login, password))
    return fetch(AUTH_URL + newState.queryParams, MY_INIT)
      .then(response => response.json())
      .then(function (json) { dispatch(receiveUserAuth(json)); return json})
      .catch(error => dispatch(errorUserAuth(error)))
  }
}

Und das haben wir
image

Korrigieren Sie mich, wenn ich falsch liege, danke

@ar53n Dann bist du gut, die Fehler werden behandelt und der Vorgang wird im Geschäft verfolgt. Das Problem mit unterbrechungsfreien Prozessen in einer Komponente gilt weiterhin, aber das ist wahrscheinlich nicht so wichtig, wenn Sie in einer Komponente oder in einem Aktions-Thunk gestartet haben.

@ar53n Sie können sich auch redux-dataloader ansehen .
Es ist für komplexe asynchrone Kettenaktionen konzipiert.
Ich hoffe es hilft!

Schließen Sie dies ab, da hier einige mögliche Lösungen veröffentlicht sind. Vielleicht möchten Sie sich heutzutage auch mit der Redux-Saga befassen!

@gaearon bricht dein Beispiel das Zeitreisen, nicht wahr?

Ich meine zum Beispiel, dass Ihr AJAX-Aufruf beim ersten Mal fehlgeschlagen ist und Sie dann die Serverseite reparieren und es wiederholen möchten

@gaearon Ich habe Ihre Lösung ausprobiert, aber wenn ich versuche, store.dispatch(...) von einer beliebigen Komponente aus aufzurufen (in meinem Fall von LoginComponent, um eine Autorisierungsanfrage zu stellen), habe ich diesen Fehler erhalten:

undefined is not an object (evaluating '_AppNavigator.AppNavigator.router')

Scheint, dass etwas nicht stimmt. Ich habe den Aktionsersteller wie folgt eingerichtet:

// actions.tsx
export const ActionCreators = {
    authenticate: (username: string, password: string) => {
        return (dispatch) => {
            return auth.login(username, password).then(
                response => {
                    dispatch(navActionCreators.login(res))
                    return response
                },
                error => {
                    throw error
                }
            )
        }
    }
}

// LoginScreen.tsx (login method)
store.dispatch(authActions.authenticate(this.state.username, this.state.password))
  .then((res) => {
     this.setState({isLoading: false})
   })
   .catch((error: Error) => {
     this.setState({
       isLoading: false,
       error: error ? error.message : 'Si è verificato un\' errore.'
     })
   })

Was vermisse ich?

@bm-software: Diese Frage sollte stattdessen bei Stack Overflow gestellt werden.

@ar53n und @sompylasar es ist eine Weile her, ich weiß, aber ich kämpfe gerade mit diesem Muster. @ar53n Was passiert in Ihrem Beispiel in der Kette, in der userAuth aufgerufen wird, wenn fetch in userAuth fehlschlägt?

Es sieht so aus, als würde .catch(error => dispatch(errorUserAuth(error))) die Aktion errerUserAuth auslösen, was großartig ist. In Redux "behandeln" wir Fehler normalerweise so. Aber in der Kette, die Sie zuvor erwähnt haben:

clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch
      return response;
    })
    .then(function(response){console.log("@RESPONSE",response);data.show(data)})
  }

data.showEvents() wird _immer_ aufgerufen, auch wenn die Benutzerauthentifizierung fehlschlägt. Ich glaube nicht, dass die meisten Leute das erwarten oder wollen, aber da die Redux-Fehlerbehandlung normalerweise durch Senden und nicht durch erneutes Auslösen erfolgt, werden Fehler geschluckt, sodass die Verkettung von Versprechen nicht wie erwartet funktioniert.

Wenn Sie erneut werfen, müssen Sie außerdem .catch() bei _jedem einzelnen Aktionsersteller-Aufruf in Ihrer App überall_ eingeben. Das Beispiel von @gaearon oben, wo er alle Fehler erneut wirft, tut dies am Ende:

// And we can now wait for the combined thunk:
store.dispatch(getUserAndTheirFirstPost(43)).then(() => {
  console.log('fetched a user and their first post')
})

Wenn _irgendetwas_ in der großen kombinierten Kette innerhalb getUserAndTheirFirstPost fehlschlägt, würde der Fehler „Nicht behandelte Versprechungsablehnung“ auftreten.

Ich denke, die einzige Antwort ist, erneut zu werfen und dann überall .catch() oder möglicherweise React 16-Fehlergrenzen zu verwenden.

@jasonrhodes

clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch

data.showEvents() wird _immer_ aufgerufen, auch wenn die Benutzerauthentifizierung fehlschlägt.

Nein, es wird nicht aufgerufen, wenn data.userAuth(data.login, data.password) ein Versprechen zurückgibt, das schließlich abgelehnt wird. Das erste Argument von .then wird aufgerufen, wenn das Versprechen erfüllt ist (es heißt onFulfilled ), das zweite, wenn das Versprechen abgelehnt wird (es heißt onRejected ) – siehe Spezifikation .

Auf der anderen Seite helfen die Fehlergrenzen von React 16 nicht bei Versprechungen, sie fangen nur synchron ausgelöste Ausnahmen ab, um sicherzustellen, dass der interne Zustand von React nicht unterbrochen wird.

@jasonrhodes Seien Sie auch ein guter Bürger der Promise-Welt, geben Sie entweder das Promise an den Aufrufer zurück (er muss dann die Fehler behandeln) oder hängen Sie ein .catch an (und behandeln Sie Fehler dort, wo das Promise erstellt wurde).

Ihr Beispiel tut nichts:

clickShowUserEvent(data) {
   Promise.resolve(data.userAuth(data.login, data.password)) // dispatch
    .then(function (response) {
      data.showEvents(); //dispatch
      return response;
    })
    .then(function(response){console.log("@RESPONSE",response);data.show(data)})
  }

@sompylasar , es war nicht mein Beispiel, es war eines, auf das Sie früher in diesem Thread geantwortet hatten. Ich weiß, es ist eine Weile her, aber ich bezog mich auf frühere Gespräche hier, weil ich diesen Thread mehr als einmal über Google-Suchen gefunden habe.

Schauen Sie noch einmal, data.showEvents() wird _immer_ aufgerufen, selbst wenn der Aufruf fetch innerhalb der Funktion userAuth fehlschlägt. Warum? Weil es innerhalb der Funktion userAuth ein .catch() gibt, das den Fehler auf die Redux-Weise behandelt: indem es eine Fehleraktion auslöst.

Dies ist der Punkt meines Beitrags: Wenn Sie Fehler von asynchronen Aktionen abfangen, damit Sie Fehleraktionen senden können, schlucken Sie und verhindern Sie, dass weitere Promise-Verkettungen ordnungsgemäß funktionieren? Oder werfen Sie erneut und zwingen jeden Anrufer dieses Aktionserstellers zu .catch() , egal was, um "unbehandelte Ablehnungen von Versprechungen" zu vermeiden?

Außerdem habe ich meiner Erfahrung nach festgestellt, dass es eine gute Idee ist, zu verstehen, was jemand weiß, bevor man ihn mit einer Spezifikation verknüpft. Vielen Dank für Ihre Antwort. Ich weiß, dass es sich um eine alte Konversation handelt, die ich ausgegraben habe, aber sie ist wichtig, da Sie sehen können, wie leicht sie übersehen werden kann!

@jasonrhodes

@sompylasar , es war nicht mein Beispiel, es war eines, auf das Sie früher in diesem Thread geantwortet hatten. Ich weiß, es ist eine Weile her, aber ich bezog mich auf frühere Gespräche hier, weil ich diesen Thread mehr als einmal über Google-Suchen gefunden habe.

Verstanden, ich entschuldige mich, habe mich nicht an den ganzen Kontext erinnert.

Weil es innerhalb der userAuth -Funktion ein .catch() gibt, das den Fehler auf die Redux-Weise behandelt: indem es eine Fehleraktion auslöst.

Verstanden, dann funktioniert das entweder wie beabsichtigt, oder Sie sollten .catch nicht dort einfügen, wenn Sie dieses Versprechen zurückgeben (und Fehler auf den Aufrufseiten behandeln), oder Sie sollten den Fehler innerhalb der .catch erneut ausgeben

Wie auch immer, Thunks selbst sind nicht gut zum Verketten geeignet. Sie sollten in einem Thunk verketten oder Sagas für komplexere asynchrone Workflows verwenden.

Ich stecke seit einiger Zeit mit dem Problem fest, die Versprechenskette zu brechen. Normalerweise würden meine Aktionsersteller eine SUCCESS-Aktion in .then() oder eine FAILURE-Aktion in .catch() derselben HTTP-Anforderung ausgeben, die vom Thunk zurückgegeben wird.

Immer wenn mein Aktionsersteller zum Catch-Block ging und ich this.props.myActionCreator().then(() => ) machte, wurde der Code in then ausgeführt, selbst wenn es Probleme in der Anfrage gab.

Um dies zu berücksichtigen, habe ich immer dafür gesorgt, dass im App-Status, der im FAILURE-Fall für diesen Aktionsersteller festgelegt ist, nach einer Fehlervariablen gesucht wird. Aber die Dinge wurden chaotisch, wenn Sie mehrere Aktionsersteller anriefen, insbesondere solche, die voneinander abhingen. Ich musste eine if-Anweisung haben, die nach vielen Fehlervariablen sucht.

Mir gefällt die Tatsache, dass die Versprechenskette nicht unterbrochen wird, indem der Fehler im catch-Block für den Rückgabewert des Aktionserstellers erneut ausgegeben wird. Dazu müssten wir jedoch .catch() aus der React-Komponente verwenden, wo der Aktionsersteller aufgerufen wird. Ich würde nichts in diesen Catch-Block schreiben, da die Fehlervariable bereits durch die Behandlung der FAILURE-Aktion in einem Reducer gesetzt ist.

Würdet ihr also, @jasonrhodes , @sompylasar , mir empfehlen, den Re-Throwing-Ansatz zu verwenden und einen leeren .catch() -Block in der Promise-Kette von Action Creator-Aufrufen in einer React-Komponente zu platzieren?

@nbkhope Um ehrlich zu sein, war dies mein größtes Problem mit Redux und bis heute habe ich keine gute Antwort gefunden. Tut mir leid, nicht hilfreicher zu sein!

Leute, in diesem Artikel findet ihr einige Thunk-Alternativen https://decembersoft.com/posts/what-is-the-right-way-to-do-asynchronous-operations-in-redux/

@gaearon

In Bezug auf Ihre erste Antwort, wie soll ich sie konvertieren, wenn ich async & await anstelle von Versprechungen verwende? Etwas wie das Folgende:

export const funcA = () => {
    return async (dispatch) => {
        const data = await doSomething(...)
        dispatch({ action: DID_SOMETHING, payload: data })
    }
}

export const funcB = () => {
    return async (dispatch) => {
        const data = await doSomethingElse(...)
        dispatch({ action: DID_SOMETHING_ELSE, payload: data })
    }
}

export const funcC = () => {
    return async (dispatch) => {
        const data = await doSomethingMore(...)
        dispatch({ action: DID_SOMETHING_MORE, payload: data })
    }
}

// how to chain funcA, funcB and funcC
const myFunc = () => {
    // execute funcA
    // when complete execute funcB
    // when complete execute funcC
}

@yingdongzhang Sie können sie wie folgt verketten:

const myFunc = () => {
  return async (dispatch) => {
    try {
      await dispatch(funcA())
      await dispatch(funcB())
      await dispatch(funcC())
    } catch (error) {
      //error handling
    }
  }
}

@Boomxx Danke funktioniert wie erwartet.

Ich frage mich, wie ich ein fetch all posts of the user machen würde.

@km16 : Dies ist ein Bug-Tracker, kein Support-System. Bei Fragen zur Verwendung verwenden Sie bitte Stack Overflow oder Reactiflux, wo viel mehr Leute bereit sind, Ihnen zu helfen - Sie erhalten wahrscheinlich schneller eine bessere Antwort. Danke!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

CellOcean picture CellOcean  ·  3Kommentare

ms88privat picture ms88privat  ·  3Kommentare

dmitry-zaets picture dmitry-zaets  ·  3Kommentare

cloudfroster picture cloudfroster  ·  3Kommentare

timdorr picture timdorr  ·  3Kommentare