You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React异步请求致cityWeathers数组长度异常问题排查

问题背景

业务逻辑为先调用monday.com的API获取数据,再根据返回结果中的城市信息调用天气API查询各城市天气,最终为每个匹配到的城市渲染天气卡片。预期cityWeathers数组应包含9个元素,但实际运行时每次得到的数组长度不固定,可能为3、5、7不等,始终无法获取完整数据。

原始问题代码
constructor(props) {
  super(props);
  this.state = {      
    setData: {},
    context:{},
    settings: {},
    myData: { boards: [] },
    cityWeathers:[]
  };     
}  

componentDidMount() {  
const getWeather = async () => monday.api('query  { boards( ids : xxxxxxxxxx ) { items { id : name column_values { text }}}}')
                      .then((res) => {  
                        this.setState({myData:res.data});
                        this.state.myData.boards.map((board) => {
                          board.items.map((item) =>{                               
                            fetch(`https://api.weatherapi.com/v1/current.json?key=xxxxxxxxxxxxxxxxxxxxxx=${item.column_values[3].text}&aqi=no`)
                            .then((res) => res.json())
                            .then((json) => {                                
                              let cityWeather ={
                              name: json.location.name,
                              temp_c:json.current.temp_c,
                              temp_f:json.current.temp_f,                          
                              condition: json.current.condition.text,
                              localTime: json.location.localtime,
                              icon: json.current.condition.icon
                            };
                            let cityWeathers = [...this.state.cityWeathers, cityWeather];
                            this.setState({cityWeathers});
                            })
                        })}
                        )
                      }
                    ) 
   getWeather()
}          
render(){             
      return(           
          <div className="App">
            <div className="container">
              <div className="row">                  
                <div className="col-md-4">  
                  {console.log(this.state.cityWeathers.length)}
                   { this.state.cityWeathers.map((city) => 
                    <WeatherCard key={city.name}  className="cards" name={city.name} 
                                temp_c={city.temp_c} temp_f={city.temp_f} icon={city.icon}
                                condition={city.condition} localtime={city.localTime} />)
                   }                                       
                </div>
              </div>
            </div>
          </div>
          )        
  }
} 
故障原因
  • 核心诱因是并行异步请求的竞态问题+错误的状态更新逻辑:循环中发起的9个天气接口请求是并行的,返回时间完全随机。每次请求返回后更新状态时,都是直接读取当前闭包里的this.state.cityWeathers旧值做追加,多个请求几乎同时返回时,后返回的请求读到的可能还是未被前序请求更新过的空数组,最终会覆盖之前已经追加的内容,导致数组长度随机。
  • setState是异步行为,调用this.setState({myData:res.data})后立刻读取this.state.myData,大概率拿到的还是初始默认值,不是接口返回的最新数据,会埋下数据读取异常的隐患。
  • 所有并行的天气请求没有做统一的收敛等待,也没有加错误捕获,任意一个请求失败都会导致对应城市的数据缺失,且没有报错日志无法感知。
  • 遍历数组执行副作用操作时错误使用了map方法,map的设计用途是映射生成新数组,纯遍历执行逻辑应该用forEach,语义更准确。
修复方案
  • 拿到monday接口的返回结果后,直接使用接口响应值做遍历,不要依赖异步更新后的state
  • 把所有城市的天气请求Promise收集起来,用Promise.all等待所有请求全部完成后,一次性更新cityWeathers状态,从根源上避免竞态覆盖问题
  • 增加请求错误捕获,避免单个请求失败导致整个逻辑崩溃
  • 遍历执行副作用时替换为forEach

修复后的参考代码:

constructor(props) {
  super(props);
  this.state = {      
    setData: {},
    context:{},
    settings: {},
    myData: { boards: [] },
    cityWeathers:[]
  };     
}  

async componentDidMount() {  
  try {
    // 先请求monday数据,直接使用响应结果,不依赖异步更新的state
    const mondayRes = await monday.api('query  { boards( ids : xxxxxxxxxx ) { items { id : name column_values { text }}}}');
    this.setState({myData: mondayRes.data});

    // 收集所有天气请求Promise
    const weatherRequestList = [];
    mondayRes.data.boards.forEach(board => {
      board.items.forEach(item => {
        const weatherReq = fetch(`https://api.weatherapi.com/v1/current.json?key=xxxxxxxxxxxxxxxxxxxxxx=${item.column_values[3].text}&aqi=no`)
          .then(res => res.json())
          .then(json => ({
            name: json.location.name,
            temp_c: json.current.temp_c,
            temp_f: json.current.temp_f,                          
            condition: json.current.condition.text,
            localTime: json.location.localtime,
            icon: json.current.condition.icon
          }))
          .catch(err => {
            console.error('获取城市天气失败:', item.column_values[3].text, err);
            return null;
          });
        weatherRequestList.push(weatherReq);
      })
    });

    // 等待所有天气请求完成,过滤掉请求失败的空值,一次性更新state
    const weatherList = await Promise.all(weatherRequestList);
    const validWeatherList = weatherList.filter(Boolean);
    this.setState({
      cityWeathers: validWeatherList
    });
  } catch (err) {
    console.error('请求monday数据失败:', err);
  }
}          

render(){             
  return(           
      <div className="App">
        <div className="container">
          <div className="row">                  
            <div className="col-md-4">  
              {this.state.cityWeathers.map((city) => 
                <WeatherCard 
                  key={city.name}
                  className="cards" 
                  name={city.name} 
                  temp_c={city.temp_c} 
                  temp_f={city.temp_f} 
                  icon={city.icon}
                  condition={city.condition} 
                  localtime={city.localTime} 
                />
              )}                                       
            </div>
          </div>
        </div>
      </div>
  )        
}

内容的提问来源于stack exchange,提问作者Kintaro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 20:31:06