React中连续Promise调用返回值被覆盖问题排查
问题根源:全局共享的配置对象导致请求参数污染
兄弟,我一眼就看穿问题出在哪了——你的API端点里那个全局的darkskyOptions对象是罪魁祸首!你每次处理请求时都直接修改这个全局对象,但对象是引用类型,这意味着所有异步请求都会共享同一个配置引用,后续请求修改属性时会直接影响到之前还在处理的请求。
举个具体场景你就懂了:
- 后续刷新页面时,你的组件依次发起三个请求:当前天气、10年前、20年前。
- 当第三个请求(20年前)进来时,你给全局的
darkskyOptions设置了time属性为20年前的日期。 - 如果第一个请求(当前天气)的
darksky.get()还没完成(异步操作的执行顺序本来就有不确定性),它会使用已经被修改过的全局配置——带着20年前的time参数去请求,自然返回旧数据,最终覆盖了你组件里的weather状态。
哪怕你在无date的请求里删除了time属性也没用,因为全局对象已经被之前的请求修改过,而且异步操作的执行顺序不一定和你发起请求的顺序完全一致。
修复方案:每次请求创建独立的配置副本
你要做的很简单:别直接修改全局的darkskyOptions,每次处理请求时都创建一个新的配置对象副本,这样每个请求都有自己独立的参数,再也不会互相干扰。修改你的API端点代码如下:
// 保留全局基础配置,但绝不直接修改它 const baseDarkskyOptions = { latitude: 38.328732, longitude: -85.764771, language: 'en', units: 'us', exclude: ['minutely', 'hourly', 'daily', 'alerts', 'flags'] } export default function handle(req, res) { const date = req.query.date || null // 每次请求都创建新的配置对象,复制基础配置的属性 const darkskyOptions = {...baseDarkskyOptions} if (!!date) { darkskyOptions.time = date } // 不需要再删除time了——因为每次都是新对象,默认就不带time属性(你的基础配置里也没有) const freshWeather = darksky.options(darkskyOptions).get() freshWeather.then((newData) => { res.json(newData) }, (err) => { console.log('Error retrieving Dark Sky weather data.') console.log(err) }) }
另外,你的组件里三个await是顺序执行的,总耗时是三个请求的时间总和,其实可以用Promise.all并行发起请求来优化加载速度(现在API端已经是独立配置了,并行也不会有问题):
async componentDidMount() { // 并行发起三个请求,同时等待所有请求完成 const [weather, weatherTen, weatherTwenty] = await Promise.all([ this.getWeather(), this.getWeather(10), this.getWeather(20) ]) this.setState({ weather, weatherTen, weatherTwenty }) }
为什么首次加载正常?
首次加载时,三个请求依次发起,而且每个请求的get()可能都在后续请求修改全局配置之前就完成了,所以参数没被污染。但后续刷新时,因为缓存、网络延迟或者Node.js事件循环的调度差异,异步请求的执行顺序被打乱,全局配置被修改时之前的请求还在处理,就出现了数据覆盖的问题。
内容的提问来源于stack exchange,提问作者David Yeiser
相关产品推荐
相关产品推荐

