Redux: Bagaimana cara mengaitkan tindakan async?

Dibuat pada 28 Apr 2016  ·  24Komentar  ·  Sumber: reduxjs/redux

Halo, Saya telah mempelajari Redux dan menghadapi masalah yang menarik? perlu membuat rantai permintaan asinkron dari Tindakan lain
1-getUser()
2-getPost()

Saya memiliki 2 solusi yang dijalankan setelah pengguna masuk .then(dispatch({type:GET_POST_REQUEST}))
atau tulis fungsi di middleWare.

Bagaimana melakukannya dengan benar?

docs

Komentar yang paling membantu

Hai! Ini adalah pelacak masalah dan bukan forum dukungan. Kami akan menghargai jika Anda bertanya di StackOverflow lain kali karena jawaban di sini hilang, tidak seperti di SO.

Karena itu, jika Anda membuat toko dengan middleware Redux Thunk , Anda dapat menulis pembuat tindakan asinkron seperti ini:

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

Kita harus memasukkan ini ke dalam FAQ.

Semua 24 komentar

Hai! Ini adalah pelacak masalah dan bukan forum dukungan. Kami akan menghargai jika Anda bertanya di StackOverflow lain kali karena jawaban di sini hilang, tidak seperti di SO.

Karena itu, jika Anda membuat toko dengan middleware Redux Thunk , Anda dapat menulis pembuat tindakan asinkron seperti ini:

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

Kita harus memasukkan ini ke dalam FAQ.

Saya memecahkan masalah ini sebagai berikut. Tanpa memodifikasi tindakan. Saya memasukkan komponen Janji.

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

Ini adalah keputusan yang benar?

Ini juga berfungsi, pola mana yang akan digunakan terserah Anda.

@ar53n Pola dengan janji dalam komponen Bereaksi memiliki beberapa kekurangan:

  • Tidak ada penanganan kesalahan (pada contoh di atas ), yaitu bagian catch . Anda mungkin mendapatkan penolakan yang tidak tertangani.
  • Itu tidak pernah terputus, misalnya ketika komponen dilepas atau terjadi beberapa tindakan yang mengubah status aplikasi.
  • Statusnya tersirat. Meskipun efek sampingnya adalah diskusi terpisah, setidaknya memiliki jejak proses yang berjalan dalam status aplikasi akan berguna.

@sompylasar terima kasih John terima kasih atas komentar Anda. Saya hanya tidak ingin mengubah tindakan sederhana. Kami memiliki 2 tindakan sederhana Authentication dan GetEvents itu adalah 2 permintaan asinkron dan bahkan berisi catch . Hanya panggilan tindakan ini saat mengklik komponen
misalnya

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

Dan kami memiliki ini
image

koreksi saya jika saya salah, terima kasih

@ ar53n Maka Anda baik-baik saja, kesalahan ditangani, dan prosesnya dilacak di toko. Masalah dengan proses yang tidak pernah terputus dalam suatu komponen masih berlaku, tetapi itu mungkin tidak terlalu penting jika Anda telah memulai dalam suatu komponen atau dalam tindakan thunk.

@ar53n Anda juga dapat melihat redux-dataloader .
Ini dirancang untuk tindakan asinkron rantai yang kompleks.
Semoga membantu!

Menutup ini karena ada beberapa solusi yang mungkin diposting di sini. Anda mungkin juga ingin melihat redux-saga saat ini!

@gaearon apakah contoh perjalanan waktu istirahat Anda bukan?

Maksud saya misalnya panggilan AJAX Anda gagal pertama kali dan kemudian Anda memperbaiki sisi server dan ingin mengulanginya

@gaearon Saya mencoba solusi Anda tetapi ketika saya mencoba memanggil store.dispatch(...) dari komponen apa pun (dalam kasus saya dari LoginComponent untuk melakukan permintaan otorisasi) saya mendapatkan kesalahan ini:

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

Sepertinya ada yang salah. Saya telah menyiapkan pembuat tindakan seperti ini:

// 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.'
     })
   })

Apa yang saya lewatkan?

@bm-software: Pertanyaan itu seharusnya ditanyakan di Stack Overflow.

@ar53n dan @sompylasar sudah lama saya tahu, tapi saya sedang berjuang dengan pola ini sekarang. @ar53n dalam contoh Anda, jika fetch di dalam userAuth gagal, apa yang terjadi dalam rantai di mana userAuth dipanggil?

Sepertinya .catch(error => dispatch(errorUserAuth(error))) akan mengirimkan tindakan errerUserAuth, yang sangat bagus. Di Redux, ini biasanya cara kami "menangani" kesalahan. Tetapi dalam rantai yang Anda sebutkan sebelumnya:

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() _selalu_ akan dipanggil, meskipun autentikasi pengguna gagal. Saya tidak berpikir itu yang diharapkan atau diinginkan kebanyakan orang, tetapi karena penanganan kesalahan Redux biasanya dilakukan dengan mengirim dan tidak melempar kembali, ia menelan kesalahan sehingga rantai janji tidak berfungsi seperti yang diharapkan.

Selain itu, jika Anda melakukan re-throw, Anda terjebak harus .catch() pada _setiap panggilan pembuat tindakan di aplikasi Anda, di mana saja_. Contoh dari @gaearon di atas, di mana ia melemparkan kembali semua kesalahan, melakukan ini di akhir:

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

Jika _anything_ dalam rantai gabungan besar di dalam getUserAndTheirFirstPost gagal, akan ada kesalahan "Penolakan janji yang tidak tertangani".

Saya pikir satu-satunya jawaban adalah melempar kembali dan kemudian .catch() ke mana-mana, atau mungkin menggunakan batas kesalahan React 16.

@jasonrhodes

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

data.showEvents() _selalu_ akan dipanggil, meskipun autentikasi pengguna gagal.

Tidak, itu tidak akan dipanggil jika data.userAuth(data.login, data.password) mengembalikan janji yang akhirnya ditolak. Argumen pertama untuk .then dipanggil ketika janji terpenuhi (disebut onFulfilled ), yang kedua ketika janji ditolak (disebut onRejected ) – lihat spesifikasi .

Di catatan lain, batas kesalahan React 16 tidak akan membantu dengan janji, mereka hanya menangkap pengecualian yang dilemparkan secara sinkron untuk memastikan bahwa status internal React tidak rusak.

@jasonrhodes Juga, jadilah warga yang baik dari dunia Promise, baik mengembalikan janji ke pemanggil (kemudian harus menangani kesalahan) atau melampirkan .catch untuk itu (dan menangani kesalahan di mana janji dibuat).

Contoh Anda tidak melakukan apa pun:

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 itu bukan contoh saya, itu adalah contoh yang telah Anda tanggapi sebelumnya di utas ini. Saya tahu ini sudah lama, tetapi saya merujuk percakapan sebelumnya di sini karena saya telah menemukan utas ini melalui pencarian Google lebih dari sekali.

Lihat lagi, data.showEvents() akan _selalu_ dipanggil meskipun panggilan fetch di dalam fungsi userAuth gagal. Mengapa? Karena ada .catch() di dalam fungsi userAuth yang menangani kesalahan dengan cara Redux: dengan mengirimkan tindakan kesalahan.

Ini adalah inti dari posting saya: ketika Anda menangkap kesalahan dari tindakan async sehingga Anda dapat mengirimkan tindakan kesalahan, apakah Anda menelan dan mencegah rantai janji lebih lanjut dari bekerja dengan benar? Atau apakah Anda melemparkan kembali dan memaksa setiap penelepon pembuat tindakan itu ke .catch() tidak peduli apa yang harus dihindari "Penolakan janji yang tidak tertangani"?

Juga, dalam pengalaman saya, saya menemukan ide yang baik untuk memahami apa yang diketahui seseorang sebelum menautkannya ke spesifikasi. Terima kasih telah menanggapi, saya tahu ini adalah percakapan lama yang saya gali, tetapi ini penting karena Anda dapat melihat betapa mudahnya melewatkannya!

@jasonrhodes

@sompylasar itu bukan contoh saya, itu adalah contoh yang telah Anda tanggapi sebelumnya di utas ini. Saya tahu ini sudah lama, tetapi saya merujuk percakapan sebelumnya di sini karena saya telah menemukan utas ini melalui pencarian Google lebih dari sekali.

Mengerti, saya minta maaf, belum mengingat seluruh konteksnya.

Karena ada .catch() di dalam fungsi userAuth yang menangani kesalahan dengan cara Redux: dengan mengirimkan tindakan kesalahan.

Mengerti, maka ini berfungsi sebagaimana dimaksud, atau Anda tidak boleh meletakkan .catch di sana jika Anda mengembalikan janji itu (dan menangani kesalahan di situs panggilan), atau Anda harus membuang kembali kesalahan di dalam .catch handler untuk menolak janji hilir.

Bagaimanapun, thunks sendiri tidak cocok untuk dirantai. Anda harus membuat rantai di dalam thunk, atau menggunakan kisah untuk alur kerja asinkron yang lebih kompleks.

Saya telah terjebak dengan masalah melanggar rantai janji untuk sementara waktu. Biasanya, pembuat tindakan saya akan mengeluarkan tindakan SUCCESS di .then() atau tindakan FAILURE di .catch() dari permintaan http yang sama yang dikembalikan dari thunk.

Setiap kali pembuat tindakan saya pergi ke blok tangkap dan saya melakukannya this.props.myActionCreator().then(() => ) , kode di dalamnya kemudian akan dijalankan bahkan jika ada masalah dalam permintaan.

Untuk memperhitungkan itu, saya selalu memastikan untuk memeriksa variabel kesalahan dalam status aplikasi yang diatur dalam kasus KEGAGALAN untuk pembuat tindakan itu. Tetapi hal-hal akan menjadi berantakan ketika Anda memanggil beberapa pembuat tindakan, terutama yang bergantung pada yang lain. Saya harus memiliki pernyataan if yang memeriksa banyak variabel kesalahan.

Saya suka fakta tidak melanggar rantai janji dengan melemparkan kembali kesalahan di blok tangkap untuk nilai pengembalian pembuat tindakan. Namun, itu mengharuskan kita menggunakan .catch() dari komponen React, tempat pembuat tindakan dipanggil. Saya tidak akan menulis apa pun di blok tangkap itu, karena variabel kesalahan sudah diatur oleh penanganan tindakan FAILURE dalam peredam.

Jadi, apakah kalian, @jasonrhodes , @sompylasar , merekomendasikan saya menggunakan pendekatan re-throwing dan menempatkan blok .catch() kosong pada rantai janji panggilan pembuat tindakan dalam komponen React?

@nbkhope jujur ​​ini adalah masalah terbesar saya dengan Redux dan sampai hari ini saya belum menemukan jawaban yang baik. Maaf untuk tidak lebih membantu!

Teman-teman, Anda dapat menemukan beberapa alternatif thunk di artikel ini https://decembersoft.com/posts/what-is-the-right-way-to-do-asynchronous-operations-in-redux/

@gaearon

Sehubungan dengan jawaban pertama Anda, bagaimana saya harus mengonversinya jika saya menggunakan async & menunggu alih-alih janji? Sesuatu seperti berikut ini:

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 Anda dapat merantainya sebagai berikut:

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

@Booxx Terima kasih bekerja seperti yang diharapkan.

Saya bertanya-tanya bagaimana saya akan melakukan fetch all posts of the user .

@km16 : Ini adalah pelacak bug, bukan sistem pendukung. Untuk pertanyaan penggunaan, silakan gunakan Stack Overflow atau Reactiflux di mana ada lebih banyak orang yang siap membantu Anda - Anda mungkin akan mendapatkan jawaban yang lebih baik lebih cepat. Terima kasih!

Apakah halaman ini membantu?
0 / 5 - 0 peringkat