在React中使用async / await不正确地更新状态

时间:2018-06-04 05:50:04

标签: javascript reactjs async-await es6-promise

我的目标是通过调用_addStock()中的componentDidMount函数,在组件状态中添加一个新的股票 - 包括报价,图表,元信息到各自的数组中。但是,在循环并调用_addStock之后,我在每个相应的数组中只有1个库存。

我可以通过在每个_addStock调用之间通过setTimeouts添加延迟来实现此功能 - 但这会违背承诺的目的。

我想到了async /等待,JavaScript会同步"同步"执行代码。但是,当我多次调用setState时,_addStock似乎只被调用一次。

发生了什么?

_fetchMeta_fetchQuote,...等返回承诺。

...

// this is what my state ends up looking like, only 1 per key when I'm expecting 4.
this.state = {
    stocks: [
        {
            symbol: 'MSFT',
            display: '...',
            color: '...'
        }
    ],
    metas: [
        {
            symbol: 'MSFT',
            name: '...',
            description: '...'
        }
    ],
    quotes: [
        {
            symbol: 'MSFT',
            price: '...',
            change: '...'
        }
    ],
    charts: [
        {
            symbol: 'MSFT',
            data: "..."
        }
    ]
}

//...

_addStock = async symbol => {
    const { metas, quotes, charts, stocks } = this.state;

    // check if stock already exists
    const hasStock = stocks.filter(s => s.symbol === symbol).length;
    if (hasStock) {
        return;
    }

    const meta = await this._fetchMeta(symbol);
    const quote = await this._fetchQuote(symbol);
    const chart = await this._fetchChart(symbol);
    const stock = {
        symbol: symbol.toUpperCase(),
        color: setStockColor(),
        display: true
    };

    this.setState({
        ...this.state,
        metas: [...metas, meta],
        quotes: [...quotes, quote],
        charts: [...charts, chart],
        stocks: [...stocks, stock]
    });
};

//...

componentDidMount() {
    const initStocks = ["FB", "MSFT", "NVDA", "AAPL"];
    initStocks.forEach(s => this._addStock(s));
}

3 个答案:

答案 0 :(得分:1)

使用Promise.all并行获取多个网址。在你的情况下,粗略的工具将是:

SELECT 
    "QUERYID",
    (SUM(UPVOTE)+ SUM(DOWNVOTE)) as "TOTAL" 
FROM
    "MY_TABLE" 
WHERE 
    "QUERYID" in (15, 7)
GROUP BY 
    "QUERYID";

答案 1 :(得分:0)

多次调用_addStock,多次调用setState。这会导致多个setState操作并行运行,而无需等待先前setState更新的确认。作为setState异步操作,您会遇到如此奇怪的行为。

要解决此问题,您可以使用async/await

再添加一个promise级别
_addStock = async symbol => {

    return new Promise((resolve,reject)){

        this.setState({
            ...this.state,
            metas: [...metas, meta],
            quotes: [...quotes, quote],
            charts: [...charts, chart],
            stocks: [...stocks, stock]
        },()=>{

            resolve(true); //give confirmation setState is updates successfully.
        });
    }
};

使用_addStock致电await

componentDidMount() {
    const initStocks = ["FB", "MSFT", "NVDA", "AAPL"];
    initStocks.forEach( async s => {
            await this._addStock(s)
    });
}

答案 2 :(得分:0)

当您在短时间间隔内多次调用setState时,所有setState调用批处理并集体执行它们,这就是为什么何时调用setState第二次第一次请求仍处于待处理状态时组件state在结尾处为空,只插入一个项目。

您可以使用以下方法异步传输数据,并且只使用最终数据更新状态一次。因为它没有为每次迭代使用状态,所以它也将保持数据一致性。

_addStock = symbols => {
  const { metas, quotes, charts, stocks } = this.state;
  // a variable to keep track how many symbols have been processed
  let count = 0;

  symbols.forEach(async symbol => {
    // check if stock already exists
    const hasStock = stocks.some(s => s.symbol === symbol);
    if (!hasStock) {
      //make all api requests in parallel
      let meta = this._fetchMeta(symbol);
      let quote = this._fetchQuote(symbol);
      let chart = this._fetchChart(symbol);
      let stock = {
        symbol: symbol.toUpperCase(),
        color: setStockColor(),
        display: true
      };

      //wait for responses
      meta = await meta;
      quote = await quote;
      chart = await chart;

      //add all values to old state
      metas.push(meta);
      quotes.push(quote);
      chart.push(chart);
      stock.push(stock);
    }

    //update count
    ++count;

    //if all values have been retrieved update state
    if(count === symbols.length){
      this.setState({
        ...this.state,
        metas: [...metas],
        quotes: [...quotes],
        charts: [...charts],
        stocks: [...stocks]
      });
    }
  });
};

componentDidMount() {
    const initStocks = ["FB", "MSFT", "NVDA", "AAPL"];
    //Pass whole array to _addStock
    this._addStock(initStocks);
}