
本文详解如何通过 async/await 和 Promise.all 正确串行/并行等待多个 fetch 请求完成,避免因忽略异步特性导致数据未就绪就执行后续逻辑的问题。
本文详解如何通过 async/await 和 promise.all 正确串行/并行等待多个 fetch 请求完成,避免因忽略异步特性导致数据未就绪就执行后续逻辑的问题。
你遇到的“第一次点击无响应、第二次才显示上一次结果、需连点三次才正常”的现象,并非代码语法错误,而是 JavaScript 异步执行机制的经典误解:你在未等待 fetch 完成的情况下,就直接访问了尚未赋值的全局变量(如 vonJson、nachJson)。
在你的原始代码中:
sendBtn.addEventListener('click', function() {
getReqVonPlz(vonPlz.value); // ⚠️ 调用但不等待!返回 Promise,立即继续
getReqNachPlz(nachPlz.value); // ⚠️ 同样不等待!
console.log(vonJson[0].display_name); // ❌ 此时 vonJson 还是 undefined!
// … 后续所有操作都基于未就绪的数据
});
尽管你为函数添加了 async 关键字,但调用时未使用 await,因此它们只是“发起请求”,而非“等待响应”。这导致后续读取 vonJson 等变量时,实际拿到的是上一次点击后遗留的旧值(或 undefined),形成“滞后一帧”的错觉。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
✅ 正确做法:将事件处理函数设为 async,并对关键异步调用显式 await。同时,推荐使用 Promise.all() 并发请求两个地址(提升性能),再统一处理结果:
✅ 优化后的完整实现
document.addEventListener('DOMContentLoaded', function () {
const vonPlz = document.getElementById("vonPlz");
const nachPlz = document.getElementById("nachPlz");
const vonOrt = document.getElementById("vonOrt");
const nachOrt = document.getElementById("nachOrt");
const km = document.getElementById("km");
const sendBtn = document.getElementById("sendBtn");
// ✅ 将 click 处理器改为 async 函数
sendBtn.addEventListener('click', async function () {
try {
// ✅ 并发获取两地地理编码数据(更高效)
const [vonRes, nachRes] = await Promise.all([
fetch(`https://nominatim.openstreetmap.org/search?format=json&country=de&postalcode=${vonPlz.value}`),
fetch(`https://nominatim.openstreetmap.org/search?format=json&country=de&postalcode=${nachPlz.value}`)
]);
// ✅ 检查 HTTP 状态
if (!vonRes.ok || !nachRes.ok) throw new Error('地址查询失败');
const [vonJson, nachJson] = await Promise.all([
vonRes.json(),
nachRes.json()
]);
// ✅ 验证响应数据有效性(防止空数组崩溃)
if (!vonJson.length || !nachJson.length) {
throw new Error('未找到对应邮政编码的位置信息,请检查输入');
}
const vonLat = parseFloat(vonJson[0].lat);
const vonLon = parseFloat(vonJson[0].lon);
const nachLat = parseFloat(nachJson[0].lat);
const nachLon = parseFloat(nachJson[0].lon);
// ✅ 获取距离(依赖前两步结果)
const distanceRes = await fetch("https://api.openrouteservice.org/v2/directions/driving-car", {
method: "POST",
headers: {
"Content-Type": "application/json; charset=UTF-8",
"Authorization": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
body: JSON.stringify({
coordinates: [[vonLon, vonLat], [nachLon, nachLat]]
})
});
if (!distanceRes.ok) throw new Error('路径计算失败');
const distanceData = await distanceRes.json();
const kilometer = (distanceData.routes?.[0]?.summary?.distance / 1000).toFixed(1); // 转为公里,保留1位小数
// ✅ 安全更新 UI
vonOrt.textContent = vonJson[0].display_name;
nachOrt.textContent = nachJson[0].display_name;
km.textContent = `${kilometer} km`;
} catch (err) {
console.error('操作失败:', err);
alert(`错误:${err.message || '请检查网络或输入格式'}`);
}
});
});
? 关键改进说明
-
移除全局状态变量(如
vonJson,kilometer):避免跨调用污染,所有数据在作用域内流转,逻辑清晰且线程安全; -
await Promise.all([...]):并发请求两地数据,比串行快近一倍,且确保两者都成功后再继续; -
HTTP 状态校验 + JSON 解析分离:先检查
res.ok,再.json(),避免网络错误被静默吞掉; -
空数据防御:检查
vonJson.length,防止undefined[0]报错; -
错误统一捕获:
try/catch包裹整个流程,提供用户友好的反馈; -
使用
textContent替代innerHTML:更安全(防 XSS),且此处无需 HTML 渲染。
⚠️ 注意事项
- 不要混用
.then()和await:你的原函数中async function内又写.then(...)是冗余的,直接return fetch(...).then(r => r.json())即可,或更简洁地return (await fetch(...)).json(); - OpenRouteService API 的
coordinates顺序是[lon, lat](经度在前),务必与 Nominatim 返回的lon/lat字段严格对应; - 生产环境应添加加载态(如按钮禁用 + loading 指示器),防止重复提交。
遵循以上模式,你的按钮首次点击即可获得完整结果——因为所有异步步骤都被显式等待、按序执行,不再依赖“碰运气”的时序巧合。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










