2017-07-25 22 views
6

簡化的問題。在Promise中調用this.setState,在結束之前呈現pending Promise。this.setState裏面的Promise引起奇怪的行爲

我的問題是:

  1. 的this.setState不立刻返回
    • 我希望它是異步,使懸而未決的承諾將首先關閉。
  2. 如果渲染函數內部有什麼東西被打破,Promise中的catch被調用。
    • 也許與1)相同的問題,它看起來像渲染仍然在this.setState被調用的承諾的上下文中。

import dummydata_rankrequests from "../dummydata/rankrequests"; 
class RankRequestList extends Component { 

    constructor(props) { 
    super(props); 

    this.state = { loading: false, data: [], error: null }; 

    this.makeRankRequestCall = this.makeRankRequestCall.bind(this); 
    this.renderItem = this.renderItem.bind(this); 
    } 

    componentDidMount() { 

    // WORKS AS EXPECTED 
    // console.log('START set'); 
    // this.setState({ data: dummydata_rankrequests.data, loading: false }); 
    // console.log('END set'); 

    this.makeRankRequestCall() 
    .then(done => { 
     // NEVER HERE 
     console.log("done"); 
    });  
    } 

    makeRankRequestCall() { 
    console.log('call makeRankRequestCall'); 
    try { 
     return new Promise((resolve, reject) => { 
     resolve(dummydata_rankrequests); 
     }) 
     .then(rankrequests => { 
     console.log('START makeRankRequestCall-rankrequests', rankrequests); 
     this.setState({ data: rankrequests.data, loading: false }); 
     console.log('END _makeRankRequestCall-rankrequests'); 
     return null; 
     }) 
     .catch(error => { 
     console.log('_makeRankRequestCall-promisecatch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
     }); 
    } catch (error) { 
     console.log('_makeRankRequestCall-catch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
    } 
    } 

    renderItem(data) { 
    const height = 200; 
    // Force a Unknown named module error here 
    return (
     <View style={[styles.item, {height: height}]}> 
     </View> 
    ); 
    } 

    render() { 
    let data = []; 
    if (this.state.data && this.state.data.length > 0) { 
     data = this.state.data.map(rr => { 
     return Object.assign({}, rr); 
     }); 
    } 
    console.log('render-data', data); 
    return (
     <View style={styles.container}> 
     <FlatList style={styles.listContainer1} 
      data={data} 
      renderItem={this.renderItem} 
     /> 
     </View> 
    ); 
    } 
} 

Currrent日誌示出了:

  • 渲染數據,[]
  • START makeRankRequestCall-rankrequests
  • 渲染數據,[。 ..]
  • _makeRankRequestCall-promisecatch錯誤:未知的命名模塊...
  • 渲染數據,[...]
  • 可能未處理的承諾

Android模擬器 「反應」:「16.0.0- alpha.12" , 「反應原生」: 「0.46.4」,

編輯:約this.setState 包裝的setTimeout也適用

setTimeout(() => { 
     this.setState({ data: respData.data, loading: false }); 
    }, 1000); 

EDIT2: 並聯 https://github.com/facebook/react-native/issues/15214

+0

我無法確切地確定什麼問題,您正在嘗試解決。你是否試圖在console.log(「done」);'執行?如果是這樣,實現它的一種方法是在準備好重新渲染時覆蓋'shouldComponentUpdate()',以便它總是返回'false','this.forceUpdate'。 https://facebook.github.io/react/docs/react-component.html#forceupdate – therobinkim

回答

1

兩個Promisethis.setState()是在javascript異步創建在反應天然github上的錯誤報告。假設您有以下代碼:

console.log(a); 
networkRequest().then(result => console.log(result)); // networkRequest() is a promise 
console.log(b); 

a和b會首先打印,然後是網絡請求的結果。

同樣,this.setState()也是異步的,如果你想this.setState()完成後執行的東西,你需要做的是:

this.setState({data: rankrequests.data},() => { 
    // Your code that needs to run after changing state 
}) 

陣營重新呈現每次this.setState()得到執行,因此你在整個承諾得到解決之前更新組件。這個問題可以通過使您的componentDidMount()爲異步功能和使用來解決有待解決的承諾:

async componentDidMount() { 
    let rankrequests; 
    try { 
    rankrequests = await this.makeRankRequestCall() // result contains your data 
    } catch(error) { 
    console.error(error); 
    } 
    this.setState({ data: rankrequests.data, loading: false },() => { 
    // anything you need to run after setting state 
    }); 
} 

希望它能幫助。

+0

我不會建議改變一些與給定組件的RN生命週期方法一樣重要的東西。使其異步可能最終成爲一個大錯誤。 – GoreDefex

+0

另外,如果您在響應狀態更新時做了任何事情,則應使用RN生命週期方法'componentDidUpdate' – GoreDefex

1

我也很難理解你在這裏試圖做什麼,所以我採取了刺。

由於this.setState()方法旨在觸發渲染,我不會調用它,直到您準備好渲染。你似乎很大程度上依賴於狀態變量是最新的,並且可以隨意使用/操縱。這裏預期的變量this.state.會在渲染時做好準備。我認爲你需要使用另一個更多的可變變量,它不與狀態和渲染綁定。當你完成時,只有這樣,你應該渲染。

這裏是你的代碼重新合作,顯示這個看起來:從「../dummydata/rankrequests」

進口dummydata_rankrequests;

類RankRequestList延伸元器件{

constructor(props) { 
    super(props); 

    /* 
     Maybe here is a good place to model incoming data the first time? 
     Then you can use that data format throughout and remove the heavier modelling 
     in the render function below 

     if (this.state.data && this.state.data.length > 0) { 
      data = this.state.data.map(rr => { 
       return Object.assign({}, rr); 
      }); 
     } 
    */ 

    this.state = { 
     error: null, 
     loading: false, 
     data: (dummydata_rankrequests || []), 
    }; 

    //binding to 'this' context here is unnecessary 
    //this.makeRankRequestCall = this.makeRankRequestCall.bind(this); 
    //this.renderItem = this.renderItem.bind(this); 
} 


componentDidMount() { 
    // this.setState({ data: dummydata_rankrequests.data, loading: false }); 

    //Context of 'this' is already present in this lifecycle component 
    this.makeRankRequestCall(this.state.data).then(returnedData => { 
     //This would have no reason to be HERE before, you were not returning anything to get here 
     //Also, 
     //should try not to use double quotes "" in Javascript 


     //Now it doesn't matter WHEN we call the render because all functionality had been returned and waited for 
     this.setState({ data: returnedData, loading: false }); 

    }).catch(error => { 
     console.log('_makeRankRequestCall-promisecatch', error); 
     this.setState({ error: RRError.getRRError(error), loading: false }); 
    }); 
} 


//I am unsure why you need a bigger call here because the import statement reads a JSON obj in without ASync wait time 
//...but just incase you need it... 
async makeRankRequestCall(currentData) { 
    try { 
     return new Promise((resolve, reject) => { 
      resolve(dummydata_rankrequests); 

     }).then(rankrequests => { 
      return Promise.resolve(rankrequests); 

     }).catch(error => { 
      return Promise.reject(error); 
     }); 

    } catch (error) { 
     return Promise.reject(error); 
    } 
} 


renderItem(data) { 
    const height = 200; 

    //This is usually where you would want to use your data set 
    return (
     <View style={[styles.item, {height: height}]} /> 
    ); 

    /* 
     //Like this 
     return { 
      <View style={[styles.item, {height: height}]}> 
       { data.item.somedataTitleOrSomething } 
      </View> 
     }; 
    */ 
} 


render() { 
    let data = []; 

    //This modelling of data on every render will cause a huge amount of heaviness and is not scalable 
    //Ideally things are already modelled here and you are just using this.state.data 
    if (this.state.data && this.state.data.length > 0) { 
     data = this.state.data.map(rr => { 
      return Object.assign({}, rr); 
     }); 
    } 
    console.log('render-data', data); 

    return (
     <View style={styles.container}> 
      <FlatList 
       data={data} 
       style={styles.listContainer1} 
       renderItem={this.renderItem.bind(this)} /> 
      { /* Much more appropriate place to bind 'this' context than above */ } 
     </View> 
    ); 
} 

}

1

setState確實是異步的。我想makeRankRequestCall應該是這樣的:

async makeRankRequestCall() { 
    console.log('call makeRankRequestCall'); 
    try { 
    const rankrequests = await new Promise((resolve, reject) => { 
     resolve(dummydata_rankrequests); 
    }); 

    console.log('START makeRankRequestCall-rankrequests', rankrequests); 
    this.setState({ data: rankrequests.data, loading: false }); 
    console.log('END _makeRankRequestCall-rankrequests'); 
    } catch(error) { 
    console.log('_makeRankRequestCall-catch', error); 
    this.setState({ error: RRError.getRRError(error), loading: false }); 
    } 
} 

其次,承諾相扶renderItem錯誤是完全正常的。在JavaScript中,任何catch塊都會捕獲代碼中任何地方拋出的任何錯誤。據specs

The throw statement throws a user-defined exception. Execution of the current function will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.

所以爲了解決這個問題,如果你希望renderItem失敗,你可以做到以下幾點:

renderItem(data) { 
    const height = 200; 
    let item = 'some_default_item'; 
    try { 
    // Force a Unknown named module error here 
    item = styles.item 
    } catch(err) { 
    console.log(err); 
    } 
    return (
    <View style={[item, {height: height}]}> 
    </View> 
); 
}