React使用Async/Await调用天气API时未获取经纬度就发请求报错问题
问题原因分析
- 你对
await的理解存在偏差:await只会暂停后续代码执行,等待它紧跟的Promise对象返回结果,并不会等待前面的非Promise异步逻辑执行完成。navigator.geolocation.getCurrentPosition是回调式异步API,调用后不会阻塞后续代码执行,回调函数会在位置获取成功后才触发,所以第一次执行fetch时,lat和long还是初始的空数组,请求参数非法返回400。 useEffect依赖项设置为[lat, long],每次setLat、setLong触发state更新都会重新执行useEffect:第一次位置获取成功后会先触发setLat更新lat值,触发useEffect第二次执行,此时long还是空数组,请求依旧报错;之后setLong触发long更新,useEffect第三次执行,两个参数都有有效值,请求正常返回。
修复方案
核心优化两个点:
- 调整
lat、long的初始值为null,和空值场景做明确区分 - 发请求前增加有效值判断,只有两个参数都不为空时才发起请求
修复后参考代码:
import { useState, useEffect } from 'react'; const Weather = (props) => { const [weather, setWeather] = useState({}); // 接口返回对象,初始值用空对象更合理 const [lat, setLat] = useState(null); // 初始值改为null const [long, setLong] = useState(null); // 初始值改为null // 只在组件挂载时执行一次位置获取 useEffect(() => { navigator.geolocation.getCurrentPosition(function (position) { setLat(position.coords.latitude); setLong(position.coords.longitude); }); }, []); // 位置更新后再发起请求 useEffect(() => { // 两个参数有一个为空就不发起请求 if (lat === null || long === null) return; const fetchData = async () => { const res = await fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${long}&appid=SomeID&units=imperial&exclude=minutely`); const result = await res.json(); setWeather(result); }; fetchData(); }, [lat, long]); return ( <React.Fragment> <div className={styles.weather}> {/* 增加可选链判断,避免weather为空时渲染报错 */} <div className={styles.weather__current}>{weather.current?.temp}</div> <div className={styles.weather__hourly}>Hourly</div> <div className={styles.weather__daily}>5 Day</div> </div> </React.Fragment> ); }; export default Weather;
如果想进一步简化逻辑,也可以把位置获取包装成Promise,合并成一个useEffect实现:
useEffect(() => { const fetchData = async () => { // 把回调式的位置API包装成Promise const position = await new Promise((resolve, reject) => { navigator.geolocation.getCurrentPosition(resolve, reject); }); const currentLat = position.coords.latitude; const currentLong = position.coords.longitude; // 拿到位置后直接发请求 const res = await fetch(`https://api.openweathermap.org/data/2.5/onecall?lat=${currentLat}&lon=${currentLong}&appid=SomeID&units=imperial&exclude=minutely`); const result = await res.json(); setWeather(result); // 后续需要用位置参数的话再存state setLat(currentLat); setLong(currentLong); }; fetchData(); }, []);
内容的提问来源于stack exchange,提问作者Angryjames
相关产品推荐
相关产品推荐

