[英]Improperly updating state with async / await in React
我的目標是通過在componentDidMount中調用_addStock()
函數,在componentDidMount
狀態下的報價單中添加新的股票-包括報價,圖表,元信息。 但是,遍歷並調用_addStock
,每個數組中只有1只股票。
我可以通過在每個_addStock
調用之間通過setTimeouts添加一個延遲來使此工作正常-但這_addStock
了promises的目的。
我以為異步/等待,JavaScript將“同步”執行代碼。 但是,當我多次調用_addStock
時,似乎只調用一次setState
。
這是怎么回事?
_fetchMeta
, _fetchQuote
,... etc返回承諾。
...
// 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));
}
用於使用Promise.all並行獲取多個URL。 在您的情況下,大致的實現是:
_addStock = async symbol => {
...
let fetchMeta = this._fetchMeta.resolve(symbol);
let fetchQuote = this._fetchQuote.resolve(symbol);
let fetchMeta = this._fetchChart.resolve(symbol);
let data = await Promise.all([
fetchMeta,
fetchQuote,
fetchChart,
]).then((data) => {
console.log(data);
// Inspect your data here setState accordingly
this.setState({ ... });
});
};
多次調用_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)
});
}
當您在短間隔內多次調用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);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.