Redux: كيف تسلسل الإجراءات غير المتزامنة؟

تم إنشاؤها على ٢٨ أبريل ٢٠١٦  ·  24تعليقات  ·  مصدر: reduxjs/redux

مرحبًا ، لقد كنت أدرس Redux وواجهت مشكلة مثيرة للاهتمام؟ بحاجة إلى إجراء سلسلة من الطلبات غير المتزامنة من الإجراءات الأخرى
1-getUser ()
2-getPost ()

لدي حلان يتم تنفيذهما بعد تسجيل دخول المستخدم. ثم (إرسال ({نوع: GET_POST_REQUEST}))
أو كتابة وظيفة في MiddleWare.

كيف نفعل ذلك بشكل صحيح؟

docs

التعليق الأكثر فائدة

أهلا! هذا هو تعقب مشكلة وليس منتدى دعم. نقدر أن تسأل عن StackOverflow في المرة القادمة لأن الإجابات هنا تضيع ، على عكس SO.

ومع ذلك ، إذا أنشأت متجرك باستخدام البرامج الوسيطة Redux Thunk ، فيمكنك كتابة منشئي إجراءات غير متزامنة مثل هذا:

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

يجب أن نضع هذا في التعليمات.

ال 24 كومينتر

أهلا! هذا هو تعقب مشكلة وليس منتدى دعم. نقدر أن تسأل عن StackOverflow في المرة القادمة لأن الإجابات هنا تضيع ، على عكس SO.

ومع ذلك ، إذا أنشأت متجرك باستخدام البرامج الوسيطة Redux Thunk ، فيمكنك كتابة منشئي إجراءات غير متزامنة مثل هذا:

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

يجب أن نضع هذا في التعليمات.

لقد حللت هذه المشكلة على النحو التالي. بدون تعديل الإجراءات. أضع في الوعد المكون.

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

هذا هو القرار الصحيح؟

يعمل هذا أيضًا ، ويعود الأمر إليك بشأن النمط الذي يجب استخدامه.

@ ar53n للنمط الذي يحمل وعدًا في مكون React عدة عيوب:

  • لا يوجد خطأ في المعالجة (في المثال أعلاه ) ، أي الجزء catch . قد تحصل على رفض غير معالج.
  • إنه غير قابل للانقطاع ، على سبيل المثال عند إلغاء تثبيت المكون أو حدوث بعض الإجراءات التي تغير حالة التطبيق.
  • حالتها ضمنية. على الرغم من أن الآثار الجانبية عبارة عن مناقشة منفصلة ، إلا أن وجود أثر لهذه العملية الجارية في حالة التطبيق سيكون مفيدًا على الأقل.

sompylasar شكرا لك جون شكرا لتعليقاتك. أنا فقط لا أريد تعديل الإجراءات البسيطة. لدينا إجراءان بسيطان Authentication و GetEvents وهو طلبان غير متزامن ويحتويان حتى على catch . فقط هذا الإجراء يستدعي عند النقر فوق المكون
على سبيل المثال

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

ولدينا هذا
image

صححني إذا كنت مخطئا ، شكرا لك

@ ar53n إذن أنت جيد ، يتم التعامل مع الأخطاء ، ويتم تتبع العملية في المتجر. لا تزال مشكلة العملية غير المنقطعة في أحد المكونات سارية ، ولكن من المحتمل ألا يكون ذلك مهمًا إذا كنت قد بدأت في مكون أو في عملية thunk.

@ ar53n يمكنك أيضًا إلقاء نظرة على أداة تحميل البيانات المستعادة .
إنه مصمم لإجراءات غير متزامنة متسلسلة معقدة.
أتمنى أن يساعد!

إغلاق هذا نظرًا لوجود بعض الحلول الممكنة المنشورة هنا. قد ترغب أيضًا في إلقاء نظرة على ملحمة redux في الوقت الحاضر!

gaearon هل المثال الخاص بك يقطع وقت السفر أليس كذلك؟

أعني على سبيل المثال فشل مكالمة AJAX في المرة الأولى ثم انتقل إلى إصلاح جانب الخادم وتريد إعادته

gaearon لقد جربت الحل الخاص بك ولكن عندما أحاول الاتصال بـ store.dispatch(...) من أي مكون (في حالتي من LoginComponent للقيام بطلب ترخيص) تلقيت هذا الخطأ:

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

يبدو أن هناك خطأ ما. لقد قمت بإعداد منشئ العمل مثل هذا:

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

ماذا ينقصني؟

@ bm-software: يجب طرح هذا السؤال على Stack Overflow بدلاً من ذلك.

@ ar53n و sompylasar لقد مرت فترة وأنا أعلم ، لكنني أعاني من هذا النمط الآن. @ ar53n في مثالك ، إذا فشل fetch داخل userAuth ، ماذا يحدث في السلسلة التي يُطلق عليها userAuth ؟

يبدو أن .catch(error => dispatch(errorUserAuth(error))) سيُرسل إجراء errerUserAuth ، وهو أمر رائع. في Redux ، هذه هي الطريقة التي "نتعامل بها" مع الأخطاء. لكن في السلسلة التي ذكرتها سابقًا:

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() _ دائمًا_ ، حتى إذا فشلت مصادقة المستخدم. لا أعتقد أن هذا ما يتوقعه أو يريده معظم الناس ، ولكن نظرًا لأن معالجة أخطاء Redux تتم عادةً عن طريق الإرسال وليس إعادة الرمي ، فإنها تبتلع الأخطاء بحيث لا يعمل تسلسل الوعد كما هو متوقع.

أيضًا ، إذا قمت بإعادة العرض ، فأنت عالق في الاضطرار إلى .catch() على _ كل مكالمة منشئ إجراء فردي في تطبيقك ، في كل مكان_. المثال من gaearon أعلاه ، حيث أعاد طرح جميع الأخطاء ، يفعل هذا في النهاية:

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

إذا فشل أي شيء في السلسلة المدمجة الكبيرة داخل getUserAndTheirFirstPost ، فسيكون هناك خطأ "رفض الوعد الذي لم تتم معالجته".

أعتقد أن الإجابة الوحيدة هي إعادة طرح ثم .catch() في كل مكان ، أو ربما استخدام حدود خطأ React 16.

تضمين التغريدة

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

سيتم استدعاء data.showEvents() _ دائمًا_ ، حتى إذا فشلت مصادقة المستخدم.

لا ، لن يتم استدعاؤه إذا data.userAuth(data.login, data.password) وعدًا تم رفضه في النهاية. يتم استدعاء الوسيطة الأولى لـ .then عند الوفاء بالوعد (يُطلق عليه onFulfilled ) ، والثاني عند رفض الوعد (يُسمى onRejected ) - راجع المواصفات .

من ناحية أخرى ، لن تساعد حدود خطأ React 16 في تقديم الوعود ، فهي تلتقط فقط استثناءات تم إلقاؤها بشكل متزامن لضمان عدم كسر حالة React الداخلية.

jasonrhodes أيضًا ، كن مواطنًا صالحًا في عالم الوعد ، إما أن تعيد الوعد إلى المتصل (يجب عليه بعد ذلك معالجة الأخطاء) أو إرفاق .catch به (وتعامل مع الأخطاء عند إنشاء الوعد).

المثال الخاص بك لا يفعل شيئًا:

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 لم يكن مثالي ، لقد كان أحد الأمثلة التي كنت تستجيب لها سابقًا في هذا الموضوع. أعلم أنه مر وقتًا طويلاً ، لكنني كنت أشير إلى المحادثات السابقة هنا لأنني صادفت هذا الموضوع عبر عمليات بحث Google أكثر من مرة.

انظر مرة أخرى ، سيتم استدعاء data.showEvents() _ دائمًا_ حتى إذا فشلت المكالمة fetch داخل الوظيفة userAuth . لماذا ا؟ نظرًا لوجود وظيفة .catch() داخل الدالة userAuth تعالج الخطأ بطريقة Redux: بإرسال إجراء خطأ.

هذا هو الهدف من رسالتي: عندما تكتشف أخطاء من إجراءات غير متزامنة حتى تتمكن من إرسال إجراءات خطأ ، فهل تبتلع وتمنع المزيد من تسلسل الوعد من العمل بشكل صحيح؟ أو هل تعيد طرح وإجبار كل متصل منشئ هذا الإجراء على .catch() بغض النظر عن ما يجب تجنبه "رفض الوعد الذي لم تتم معالجته"؟

أيضًا ، من خلال تجربتي ، وجدت أنه من الجيد فهم ما يعرفه شخص ما قبل ربطه بالمواصفات. شكرًا على الرد ، أعلم أنها محادثة قديمة بحثت عنها ، لكنها محادثة مهمة حيث يمكنك أن ترى مدى سهولة تفويتها!

تضمين التغريدة

sompylasar لم يكن مثالي ، لقد كان أحد الأمثلة التي كنت تستجيب لها سابقًا في هذا الموضوع. أعلم أنه مر وقتًا طويلاً ، لكنني كنت أشير إلى المحادثات السابقة هنا لأنني صادفت هذا الموضوع عبر عمليات بحث Google أكثر من مرة.

حسنًا ، أعتذر ، لم أتذكر السياق بالكامل.

نظرًا لوجود وظيفة .catch() داخل الدالة userAuth تعالج الخطأ بطريقة إعادة التشغيل: بإرسال إجراء خطأ.

حسنًا ، فإما أن يعمل هذا على النحو المنشود ، أو لا يجب عليك وضع .catch هناك إذا أعدت هذا الوعد (وتعالجت الأخطاء في مواقع الاتصال) ، أو يجب عليك إعادة طرح الخطأ داخل .catch handler لرفض الوعد النهائي.

على أي حال ، فإن thunks نفسها ليست مناسبة تمامًا للتسلسل. يجب عليك التسلسل داخل thunk ، أو استخدام الملاحم لمزيد من عمليات سير العمل غير المتزامن المعقدة.

لقد علقت مع مشكلة كسر سلسلة الوعد لفترة من الوقت. عادةً ما يرسل منشئو الإجراء الخاص بي إجراء SUCCESS في .then () أو إجراء FAILURE في .catch () لطلب http نفسه الذي يتم إرجاعه من thunk.

كلما ذهب منشئ الإجراء الخاص بي إلى كتلة catch وقمت بعمل this.props.myActionCreator().then(() => ) ، سيتم تنفيذ الكود الموجود داخل ذلك الوقت حتى لو كانت هناك مشاكل في الطلب.

لحساب ذلك ، كنت أتأكد دائمًا من التحقق من وجود متغير خطأ في حالة التطبيق التي تم تعيينها في حالة الفشل لمنشئ الإجراء هذا. لكن الأمور ستصبح فوضوية عندما تتصل بالعديد من منشئي الإجراءات ، خاصة أولئك الذين يعتمدون على الآخرين. كان يجب أن يكون لدي عبارة if تتحقق من العديد من متغيرات الخطأ.

أحب حقيقة عدم كسر سلسلة الوعد من خلال إعادة طرح الخطأ في كتلة catch للقيمة المرجعة لمنشئ الإجراء. ومع ذلك ، سيتطلب ذلك منا استخدام .catch () من مكون React ، حيث يُطلق على منشئ الإجراء. لن يكون لدي أي شيء مكتوب في كتلة catch ، نظرًا لأن متغير الخطأ قد تم تعيينه بالفعل من خلال معالجة إجراء FAILURE في مخفض.

فهل أنتم يا رفاق ، jasonrhodes ، sompylasar ، توصيني باستخدام نهج إعادة الرمي ووضع كتلة .catch () فارغة في سلسلة الوعد لاستدعاءات منشئ المحتوى في مكون React؟

nbkhope لأكون صادقًا ، كانت هذه أكبر مشكلتي مع Redux وحتى يومنا هذا لم أحصل على إجابة جيدة. آسف لأن لا تكون أكثر فائدة!

يا رفاق ، يمكنك العثور على بعض البدائل الرائعة في هذه المقالة https://decembersoft.com/posts/what-is-the-right-way-to-do-asynchronous-operations-in-redux/

تضمين التغريدة

فيما يتعلق بإجابتك الأولى ، كيف يمكنني تحويلها إذا كنت أستخدم غير متزامن وانتظر بدلاً من الوعود؟ شيء من هذا القبيل:

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 يمكنك ربطها على النحو التالي:

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

Boomxx شكرا لك يعمل كما هو متوقع.

أتساءل كيف سأفعل fetch all posts of the user .

@ km16 : هذا هو أداة تعقب الأخطاء ، وليس نظام دعم. بالنسبة لأسئلة الاستخدام ، يرجى استخدام Stack Overflow أو Reactiflux حيث يوجد الكثير من الأشخاص المستعدين لمساعدتك - ربما تحصل على إجابة أفضل بشكل أسرع. شكرا!

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات