2015-08-27 7 views
12

में नेस्टेड एपीआई कॉल को कैसे संभालें I अंग्रेजी सीखने वाली साइट के लिए पदों के निर्माण और संपादन को संभालने के लिए फेसबुक के फ्लक्स डिस्पैचर का उपयोग करके एक सरल सीआरयूडी ऐप बना रहा हूं। मैं वर्तमान में एक एपीआई कि इस तरह दिखता है के साथ काम कर रहा हूँ:फ्लक्स

/posts/:post_id 
/posts/:post_id/sentences 
/sentences/:sentence_id/words 
/sentences/:sentence_id/grammars 

अनुप्रयोग के लिए शो और संपादित पृष्ठों पर, मैं के सभी के रूप में दिए गए पद के लिए सभी जानकारी को दिखाने के लिए और साथ ही सक्षम होने के लिए करना चाहते हैं यह वाक्यों और वाक्यों के शब्दों और व्याकरण विवरण सभी एक ही पृष्ठ पर है।

जो मुद्दा मैं मार रहा हूं वह यह पता लगा रहा है कि इस डेटा को इकट्ठा करने के लिए आवश्यक सभी एसिंक कॉल कैसे शुरू करें, और उसके बाद मुझे सभी स्टोर्स से डेटा को एक ही ऑब्जेक्ट में लिखना है जिसे मैं राज्य के रूप में सेट कर सकता हूं मेरा शीर्ष स्तर घटक। मुझे क्या करना करने की कोशिश कर रहा है की एक वर्तमान (भयानक) उदाहरण यह है:

शीर्ष स्तर PostsShowView:

class PostsShow extends React.Component { 
    componentWillMount() { 
    // this id is populated by react-router when the app hits the /posts/:id route 
    PostsActions.get({id: this.props.params.id}); 

    PostsStore.addChangeListener(this._handlePostsStoreChange); 
    SentencesStore.addChangeListener(this._handleSentencesStoreChange); 
    GrammarsStore.addChangeListener(this._handleGrammarsStoreChange); 
    WordsStore.addChangeListener(this._handleWordsStoreChange); 
    } 

    componentWillUnmount() { 
    PostsStore.removeChangeListener(this._handlePostsStoreChange); 
    SentencesStore.removeChangeListener(this._handleSentencesStoreChange); 
    GrammarsStore.removeChangeListener(this._handleGrammarsStoreChange); 
    WordsStore.removeChangeListener(this._handleWordsStoreChange); 
    } 

    _handlePostsStoreChange() { 
    let posts = PostsStore.getState().posts; 
    let post = posts[this.props.params.id]; 

    this.setState({post: post}); 

    SentencesActions.fetch({postId: post.id}); 
    } 

    _handleSentencesStoreChange() { 
    let sentences = SentencesStore.getState().sentences; 

    this.setState(function(state, sentences) { 
     state.post.sentences = sentences; 
    }); 

    sentences.forEach((sentence) => { 
     GrammarsActions.fetch({sentenceId: sentence.id}) 
     WordsActions.fetch({sentenceId: sentence.id}) 
    }) 
    } 

    _handleGrammarsStoreChange() { 
    let grammars = GrammarsStore.getState().grammars; 

    this.setState(function(state, grammars) { 
     state.post.grammars = grammars; 
    }); 
    } 

    _handleWordsStoreChange() { 
    let words = WordsStore.getState().words; 

    this.setState(function(state, words) { 
     state.post.words = words; 
    }); 
    } 
} 

और यहाँ है मेरी PostsActions.js - अन्य संस्थाओं (वाक्य, व्याकरण,

let api = require('api'); 

class PostsActions { 
    get(params = {}) { 
    this._dispatcher.dispatch({ 
     actionType: AdminAppConstants.FETCHING_POST 
    }); 

    api.posts.fetch(params, (err, res) => { 
     let payload, post; 

     if (err) { 
     payload = { 
      actionType: AdminAppConstants.FETCH_POST_FAILURE 
     } 
     } 
     else { 
     post = res.body; 

     payload = { 
      actionType: AdminAppConstants.FETCH_POST_SUCCESS, 
      post: post 
     } 
     } 

     this._dispatcher.dispatch(payload) 
    }); 
    } 
} 

मुख्य मुद्दा यह है कि फ्लक्स डिस्पैचर फेंकता एक अपरिवर्तनीय त्रुटि SentencesActions.fetch_handlePostsStoreChange कॉलबैक में कहा जाता है "एक प्रेषण के बीच में प्रेषण नहीं कर सकते" है: शब्द) भी इसी तरह ActionCreators है कि एक समान तरीके से काम करते हैं becau से पहले की क्रिया के लिए प्रेषण कॉलबैक से पहले प्रेषण क्रिया विधि एक प्रेषण को ट्रिगर करती है।

मुझे पता है कि मैं _.defer या setTimeout की तरह कुछ का उपयोग करके इसे ठीक कर सकते हैं - लेकिन वास्तव में ऐसा लगता है कि जैसे मैं बस यहाँ मुद्दा पैचिंग कर रहा हूँ। इसके अलावा, मैंने कार्यों में अपने सभी fetching तर्क को करने पर विचार किया, लेकिन यह भी सही नहीं लग रहा था, और त्रुटि को और अधिक कठिन संभालने में मदद करेगा। मेरे पास मेरी प्रत्येक संस्थाएं अपने स्वयं के स्टोर और कार्यों में अलग हो गई हैं - क्या प्रत्येक इकाई के संबंधित स्टोर से मुझे जो चाहिए उसे लिखने के लिए घटक स्तर में कुछ रास्ता नहीं होना चाहिए?

किसी भी व्यक्ति से किसी भी सलाह के लिए खोलें जिसने कुछ ऐसा किया है!

+0

क्या आपने 'waitFor' का उपयोग करने का प्रयास किया है? https://facebook.github.io/flux/docs/dispatcher.html – knowbody

+0

@nownow हाँ, मैंने 'waitFor' का उपयोग करने का प्रयास किया है, लेकिन वास्तव में यह समस्या का समाधान नहीं कर रहा था, क्योंकि मुद्दा यह है कि पहले व्यक्ति को खत्म करने से पहले दूसरी कार्रवाई भेज दी जाती है। हालांकि, शायद 'WaitFor' की मेरी समझ गलत है और मैं इसे सही तरीके से उपयोग नहीं कर रहा हूं? – joeellis

+1

@ जॉयेलिस: क्या आपके लिए एक जेएसफ़िल्ड डेमो को एक साथ रखना संभव है कृपया अपनी समस्या की स्थिति का प्रदर्शन करें? –

उत्तर

5

लेकिन नहीं, वहाँ एक प्रेषण के बीच में एक कार्रवाई बनाने के लिए कोई हैक है, और इस डिजाइन कर रहा है। कार्रवाइयों को ऐसी चीजें नहीं माना जाता है जो परिवर्तन का कारण बनती हैं। उन्हें एक समाचार पत्र की तरह माना जाता है जो बाहरी दुनिया में बदलाव के आवेदन को सूचित करता है, और फिर आवेदन उस समाचार का जवाब देता है। स्टोर खुद में परिवर्तन का कारण बनता है। क्रियाएं उन्हें सूचित करें।

इसके अलावा

अवयव जब डेटा लाने के लिए निर्णय लेने नहीं होना चाहिए। यह दृश्य परत में अनुप्रयोग तर्क है।

विधेयक फिशर, फ्लक्स https://stackoverflow.com/a/26581808/4258088

के निर्माता अपने घटक जब डेटा लाने के लिए निर्णय लेने है। यह बुरा अभ्यास है। आपको मूल रूप से क्या करना चाहिए, यह आपके घटक को क्रियाओं के माध्यम से बता रहा है कि उसे किस डेटा की आवश्यकता है।

स्टोर सभी आवश्यक डेटा जमा/लाने के लिए जिम्मेदार होना चाहिए।हालांकि यह ध्यान रखना महत्वपूर्ण है कि स्टोर के बाद एपीआई कॉल के माध्यम से डेटा का अनुरोध किया गया था, प्रतिक्रिया को सीधे कार्रवाई को बचाने/स्टोर को बचाने के विरोध में एक क्रिया को ट्रिगर करना चाहिए।

आपका भंडार कुछ इस तरह दिखाई देगा:

class Posts { 
    constructor() { 
    this.posts = []; 

    this.bindListeners({ 
     handlePostNeeded: PostsAction.POST_NEEDED, 
     handleNewPost: PostsAction.NEW_POST 
    }); 
    } 

    handlePostNeeded(id) { 
    if(postNotThereYet){ 
     api.posts.fetch(id, (err, res) => { 
     //Code 
     if(success){ 
      PostsAction.newPost(payLoad); 
     } 
     } 
    } 
    } 

    handleNewPost(post) { 
    //code that saves post 
    SentencesActions.needSentencesFor(post.id); 
    } 
} 

तुम सब तो क्या करने की जरूरत दुकानों के लिए सुन रहा है। इसके अलावा यदि आप एक ढांचे का उपयोग करते हैं और आपको परिवर्तन ईवेंट (मैन्युअल रूप से) को छोड़ने की आवश्यकता होती है तो यह भी निर्भर करता है।

+1

इस के लिए धन्यवाद (और अन्य सभी उत्तरों के लिए)। ऐसा लगता है कि मैं गलत कहां जा रहा था, मैं मान रहा था कि शीर्ष स्तर के स्मार्ट घटक को व्यू-कंट्रोलर प्रकार के रूप में कार्य करना चाहिए, यानी यह उन सभी कार्यों और दुकानों के खिलाफ कार्य करने में सक्षम होना चाहिए जो इसे सभी राज्यों को लिखने/इकट्ठा करने की ज़रूरत है बच्चों के घटकों का उपयोग करने की जरूरत है। ऐसा लगता है कि विचार दोषपूर्ण है, और स्टोर वास्तव में इन async अनुरोधों को संभालना चाहिए। हालांकि यह एक शर्म की बात है, क्योंकि मेरे पास कार्यों के लिए ज़िम्मेदारियां होंगी, लेकिन हां, मुझे लगता है कि मैंने साबित कर दिया है कि वर्तमान प्रवाह प्रतिमान के साथ यह संभव नहीं है। – joeellis

2

मुझे लगता है कि आपके पास अपने डेटा मॉडल और कुछ पीओजेओ की ऑब्जेक्ट्स को आपके ऑब्जेक्ट के उदाहरणों को प्रतिबिंबित करने वाले विभिन्न स्टोर होना चाहिए। इस प्रकार, आपके Post वस्तु एक getSentence() तरीकों जो बारी-बारी true या `झूठी मौसम सभी datas प्राप्त किए गए किया गया है या नहीं लौटने SentenceStore.get(id) आदि तुम बस अपने Post वस्तु के लिए इस तरह isReady() के रूप में एक विधि जोड़ने की जरूरत है फोन होगा।

यहाँ ImmutableJS उपयोग कर रहा है एक बुनियादी कार्यान्वयन:

PostSore.js

var _posts = Immutable.OrderedMap(); //key = post ID, value = Post 

class Post extends Immutable.Record({ 
    'id': undefined, 
    'sentences': Immutable.List(), 
}) { 

    getSentences() { 
     return SentenceStore.getByPost(this.id) 
    } 

    isReady() { 
     return this.getSentences().size > 0; 
    } 
} 

var PostStore = assign({}, EventEmitter.prototype, { 

    get: function(id) { 
     if (!_posts.has(id)) { //we de not have the post in cache 
      PostAPI.get(id); //fetch asynchronously the post 
      return new Post() //return an empty Post for now 
     } 
     return _post.get(id); 
    } 
}) 

SentenceStore.js

var _sentences = Immutable.OrderedMap(); //key = postID, value = sentence list 

class Sentence extends Immutable.Record({ 
    'id': undefined, 
    'post_id': undefined, 
    'words': Immutable.List(), 
}) { 

    getWords() { 
     return WordsStore.getBySentence(this.id) 
    } 

    isReady() { 
     return this.getWords().size > 0; 
    } 
} 

var SentenceStore = assign({}, EventEmitter.prototype, { 

    getByPost: function(postId) { 
     if (!_sentences.has(postId)) { //we de not have the sentences for this post yet 
      SentenceAPI.getByPost(postId); //fetch asynchronously the sentences for this post 
      return Immutable.List() //return an empty list for now 
     } 
     return _sentences.get(postId); 
    } 
}) 

var _setSentence = function(sentenceData) { 
    _sentences = _sentences.set(sentenceData.post_id, new Bar(sentenceData)); 
}; 

var _setSentences = function(sentenceList) { 
    sentenceList.forEach(function (sentenceData) { 
     _setSentence(sentenceData); 
    }); 
}; 

SentenceStore.dispatchToken = AppDispatcher.register(function(action) { 
    switch (action.type) 
    { 
     case ActionTypes.SENTENCES_LIST_RECEIVED: 
      _setSentences(action.sentences); 
      SentenceStore.emitChange(); 
      break; 
    } 
}); 

WordStore.js

0,123,
var _words = Immutable.OrderedMap(); //key = sentence id, value = list of words 

class Word extends Immutable.Record({ 
    'id': undefined, 
    'sentence_id': undefined, 
    'text': undefined, 
}) { 

    isReady() { 
     return this.id != undefined 
    } 
} 

var WordStore = assign({}, EventEmitter.prototype, { 

    getBySentence: function(sentenceId) { 
     if (!_words.has(sentenceId)) { //we de not have the words for this sentence yet 
      WordAPI.getBySentence(sentenceId); //fetch asynchronously the words for this sentence 
      return Immutable.List() //return an empty list for now 
     } 
     return _words.get(sentenceId); 
    } 

}); 

var _setWord = function(wordData) { 
    _words = _words.set(wordData.sentence_id, new Word(wordData)); 
}; 

var _setWords = function(wordList) { 
    wordList.forEach(function (wordData) { 
     _setWord(wordData); 
    }); 
}; 

WordStore.dispatchToken = AppDispatcher.register(function(action) { 
    switch (action.type) 
    { 
     case ActionTypes.WORDS_LIST_RECEIVED: 
      _setWords(action.words); 
      WordStore.emitChange(); 
      break; 
    } 

}); 

ऐसा करने से, आप केवल सुनने के लिए करने के लिए ऊपर भंडार अपने घटक में बदल सकते हैं और कुछ इस तरह (छद्म कोड)

YourComponents.jsx

getInitialState: 
    return {post: PostStore.get(your_post_id)} 

componentDidMount: 
    add listener to PostStore, SentenceStore and WordStore via this._onChange 

componentWillUnmount: 
    remove listener to PostStore, SentenceStore and WordStore 

render: 
    if this.state.post.isReady() //all data has been fetched 

    else 
     display a spinner   

_onChange: 
    this.setState({post. PostStore.get(your_post_id)}) 

जब उपयोगकर्ता लिखने की जरूरत है पृष्ठ को हिट करता है, PostStore पहले अजाक्स के माध्यम से पोस्ट ऑब्जेक्ट पुनर्प्राप्त करेगा और आवश्यक डेटा SentenceStore और WordStore द्वारा लोड किया जाएगा। हम उन्हें सुन रहे हैं और Post की isReady() विधि केवल रिटर्न के बाद से true जब पोस्ट की वाक्य तैयार हैं, और Sentence की isReady() विधि केवल true लौटाता है जब इसके सभी शब्द लोड किया गया है, तो आप ऐसा करने के लिए :) बस को स्पिनर के लिए इंतजार कुछ भी नहीं है जब आपका डेटा तैयार हो जाए तो अपनी पोस्ट द्वारा प्रतिस्थापित किया जाए!

0

मुझे नहीं पता कि आपकी एप्लिकेशन स्थिति कैसे संभाली जाती है, लेकिन मेरे लिए फ्लक्स के साथ समस्याओं का सामना करने के दौरान हमेशा सिस्टम सबसे अच्छा काम करता है, ताकि स्टोर में अधिक राज्य और अधिक तर्क हो सके। मैंने इसे कई बार पाने की कोशिश की है, और यह हमेशा मुझे काटने से समाप्त होता है। तो सबसे सरल उदाहरण में, मैं एक ऐसी क्रिया को प्रेषित करता हूं जो पूरे अनुरोध को संभालने के साथ-साथ किसी भी राज्य के साथ चलती है। यहाँ एक बहुत ही सरल उदाहरण है, होना चाहिए कि अपेक्षाकृत फ्लक्स ढांचा-अज्ञेयवाद:

var store = { 
    loading_state: 'idle', 
    thing_you_want_to_fetch_1: {}, 
    thing_you_want_to_fetch_2: {} 
} 

handleGetSomethingAsync(options) { 
    // do something with options 
    store.loading_state = 'loading' 
    request.get('/some/url', function(err, res) { 
    if (err) { 
     store.loading_state = 'error'; 
    } else { 
     store.thing_you_want_to_fetch_1 = res.body; 
     request.get('/some/other/url', function(error, response) { 
     if (error) { 
      store.loading_state = 'error'; 
     } else { 
      store.thing_you_want_to_fetch_2 = response.body; 
      store.loading_state = 'idle'; 
     } 
     } 
    } 
    } 
} 
फिर अपने प्रतिक्रिया घटकों में

आप store.loading_state उपयोग यह निर्धारित करने लोडिंग स्पिनर किसी तरह का, एक त्रुटि, या के रूप में डेटा प्रस्तुत करने के लिए है कि क्या सामान्य।

ध्यान दें कि इस मामले में कार्रवाई एक विकल्प विधि को विकल्प ऑब्जेक्ट को पास करने से कहीं अधिक कुछ नहीं करती है जो तब एक ही स्थान पर एकाधिक अनुरोधों से जुड़े सभी तर्क और राज्य को संभालती है।

मुझे बताएं कि क्या मैं इनमें से किसी भी बेहतर व्याख्या कर सकता हूं।

संबंधित मुद्दे