嵌套axios调用首次执行时EJS渲染变量未定义报错问题
问题原因
- 核心问题是Promise嵌套时没有打通调用链:你在第一层axios的
then回调里发起了第二层axios请求,但没有将这个内层Promise返回,外层的异步流程没有真正等待内层请求执行完成。首次访问第三方接口时需要做DNS解析、TCP连接建立、SSL握手,整体请求耗时更长,时序问题直接暴露,render逻辑执行时h1等从接口取值的变量还没完成赋值,就会报undefined错误。 - 第二次及之后访问时,Node.js、操作系统都已经缓存了接口的连接信息,请求耗时大幅缩短,回调执行顺序刚好和你预期一致,看起来运行正常,但这属于概率性正常,只要接口延迟出现波动问题就会复现。
- 你在外层作用域声明共享let变量、靠异步回调修改变量值的写法本身存在并发风险:如果用户短时间内触发多个同路由请求,不同请求的变量值会互相覆盖,直接返回错误的页面数据。
- 你当前的写法完全没有异常捕获逻辑,只要任意一个接口请求失败,服务会直接抛出未捕获的Promise错误,甚至导致进程退出。
修复方案
推荐用async/await写法重构,逻辑是线性的,完全避免嵌套Promise忘return导致的链断裂问题,也不需要在外层声明一堆待赋值的共享变量:
app.get('/private_:city/', async function(req, response) { try { const name = req.params.city; const nameCapitalised = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase(); // 等待第一个地理接口返回 const geoRes = await axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${nameCapitalised}&limit=1&appid=${apiKeyOpenWeather}`); const { lat, lon } = geoRes.data[0]; console.log('first', geoRes.data[0]); // 等待第二个天气接口返回 const weatherRes = await axios.get(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=daily,minutely&units=metric&lang=it&appid=${apiKeyOpenWeather}`); console.log('second'); const currentHourData = weatherRes.data.hourly[0]; // 直接在当前作用域处理要渲染的变量,不需要共享外层变量 const h1 = new Date(currentHourData.dt * 1000).getHours(); const w1 = currentHourData.weather[0].description; const t1 = currentHourData.temp; const uv1 = currentHourData.uvi; const rp1 = Math.trunc(currentHourData.pop * 100); const ws1 = currentHourData.wind_speed; console.log('third'); // 所有变量准备完成再渲染 response.render('private.ejs', { title: nameCapitalised, t1, h1, w1, uv1, rp1, ws1, }); } catch (err) { // 加错误处理,接口失败时返回错误提示,避免服务崩溃 console.error('接口请求失败', err); response.status(500).send('获取天气数据失败,请稍后重试'); } });
如果坚持用原来的then链式写法,必须保证每一层异步Promise都被return,把整条链打通,同样不要用外层共享变量:
app.get('/private_:city/', function(req, response) { const name = req.params.city; const nameCapitalised = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase(); axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${nameCapitalised}&limit=1&appid=${apiKeyOpenWeather}`) .then(geoRes => { console.log('first', geoRes.data[0]); const { lat, lon } = geoRes.data[0]; // 必须return内层的Promise,外层链才会等待它执行完成 return axios.get(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=daily,minutely&units=metric&lang=it&appid=${apiKeyOpenWeather}`); }) .then(weatherRes => { console.log('second'); const currentHourData = weatherRes.data.hourly[0]; // 把处理好的渲染数据return给下一个then return { h1: new Date(currentHourData.dt * 1000).getHours(), w1: currentHourData.weather[0].description, t1: currentHourData.temp, uv1: currentHourData.uvi, rp1: Math.trunc(currentHourData.pop * 100), ws1: currentHourData.wind_speed }; }) .then(renderData => { console.log('third'); response.render('private.ejs', { title: nameCapitalised, ...renderData }); }) .catch(err => { console.error('接口请求失败', err); response.status(500).send('获取天气数据失败,请稍后重试'); }); });
注意:不要用外层作用域的共享变量在异步回调里传值,Node.js服务端的路由回调作用域会被并发请求复用,这种写法必然会出现请求间数据串扰的问题。
内容的提问来源于stack exchange,提问作者gab
相关产品推荐
相关产品推荐

