Promise中的this.setState导致奇怪的行为

时间:2017-07-25 19:53:37

标签: react-native

简化问题。在Promise中调用this.setState,在结束之前呈现为Promise。

我的问题是:

  1. 不会立即返回this.setState
    • 我希望它是异步的,这样就可以先关闭待处理的承诺。
  2. 如果渲染函数内部出现异常,则调用Promise中的catch。
    • 也许同样的问题1)似乎渲染仍然在调用this.setState的promise的上下文中。
  3. 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日志显示:

    • render-data,[]
    • START makeRankRequestCall-rankrequests
    • render-data,[...]
    • _makeRankRequestCall-promisecatch错误:未知的命名模块...
    • render-data,[...]
    • 可能未处理的承诺

    Android模拟器 &#34;反应&#34;:&#34; 16.0.0-alpha.12&#34;, &#34; react-native&#34;:&#34; 0.46.4&#34;,

    修改: 在this.setState周围包装setTimeout也可以正常工作

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

    EDIT2 : 在react-native github中并行创建了一个bug报告 https://github.com/facebook/react-native/issues/15214

3 个答案:

答案 0 :(得分:1)

Promisethis.setState()在javascript中都是异步的。比如,如果您有以下代码:

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()时React重新渲染,因此在整个承诺得到解决之前,您将更新组件。通过将componentDidMount()作为异步函数并使用await来解决承诺,可以解决此问题:

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

希望它有所帮助。

答案 1 :(得分:1)

我也很难理解你在这里尝试做什么,所以我抓了一把。

由于this.setState()方法旨在触发渲染,因此在准备渲染之前我不会调用它。您似乎在很大程度上依赖于状态变量是最新的并且能够随意使用/操作。此处的this.state.变量的预期行为将在渲染时准备就绪。我认为你需要使用另一个与状态和渲染无关的更多可变变量。当你完成时,只有那时,你应该渲染。

以下是您重新编写代码的代码:

从“../ dummydata / rankrequests”导入dummydata_rankrequests;

class RankRequestList扩展了Component {

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

}

答案 2 :(得分: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块都将捕获代码中任何位置抛出的任何错误。根据{{​​3}}:

  

throw语句抛出用户定义的异常。当前函数的执行将停止(抛出后的语句将被执行),并且控制将被传递给调用堆栈中的第一个catch块。如果调用函数之间不存在catch块,则程序将终止。

因此,为了解决问题,如果您希望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>
  );
}