如何在 D3 v7 中正确处理 CSV 异步加载并确保全局数组可用

本文详解 d3 v7 中 `d3.csv()` 的异步特性导致全局数组在函数外仍为 `undefined` 的根本原因,并提供基于 `async/await` 的标准解决方案,确保数据加载完成后再执行地图标记等依赖操作。

D3 v7 的 d3.csv() 返回一个 Promise,而非同步填充数据——这是问题的核心。您声明了全局数组(如 projectName = []),并在 data() 函数中尝试赋值,但由于未等待 Promise 解析完成,createMarkers() 就已执行,此时全局数组仍是空的(访问 lat[z] 自然得到 undefined,进而导致 NaN 或报错)。

要修复此问题,必须让 initMap() 等待 CSV 加载完毕。关键修改有三处:

  1. data() 函数需返回 d3.csv() Promise,并统一使用现代箭头函数解析器(避免旧式回调嵌套);
  2. initMap() 必须 await data(),确保全局数组已填充;
  3. createMarkers() 需接收 map 实例作为参数(因 map 在 initMap() 内定义,非全局)。

以下是修正后的完整代码(含健壮性增强):

// 请求 Google Maps 库
const { Map, InfoWindow } = await google.maps.importLibrary("maps");
const { LatLng } = await google.maps.importLibrary("core");
const { AdvancedMarkerElement, PinElement } = await google.maps.importLibrary("marker");

// 全局数组(保持声明)
let projectName = [];
let artist = [];
let lat = [];
let long = [];
let markers = [];

// ✅ 修正:data() 返回 Promise,使用 d3.csv 的现代解析方式
async function data() {
  return d3.csv("/data/single.csv", (d) => ({
    projectName: d.Project_Name,  // 字符串,勿用 +d.Project_Name(会转 NaN)
    artist: d.Artist,
    lat: parseFloat(d.Latitude),   // 显式 parseFloat 更安全
    long: parseFloat(d.Longitude),
  }));
}

// ✅ 修正:createMarkers 接收 map 参数,避免作用域问题
function createMarkers(m) {
  for (let z = 0; z < lat.length; z++) {
    if (isNaN(lat[z]) || isNaN(long[z])) continue; // 跳过无效坐标
    markers.push(
      new google.maps.Marker({
        position: { lat: lat[z], lng: long[z] }, // 直接传对象,更简洁
        map: m,
        title: projectName[z] || "Unknown Project",
      })
    );
  }
}

// ✅ 修正:initMap 使用 async/await,确保顺序执行
async function initMap() {
  const map = new Map(document.getElementById("map"), {
    zoom: 11,
    center: { lat: 37.4239163, lng: -122.0947209 },
    mapId: "[MAP ID]",
    mapTypeControl: false,
  });

  try {
    // ? 关键:等待 CSV 加载并填充全局数组
    const csvData = await data();

    // 填充全局数组(推荐:直接赋值,避免手动循环索引)
    projectName = csvData.map(d => d.projectName);
    artist = csvData.map(d => d.artist);
    lat = csvData.map(d => d.lat);
    long = csvData.map(d => d.long);

    // 创建标记
    createMarkers(map);

  } catch (error) {
    console.error("CSV 加载失败:", error);
    alert("地图数据加载异常,请检查 CSV 文件路径和格式。");
  }
}

initMap();

注意事项与最佳实践:

  • ❌ 避免在 d3.csv 回调中混用旧式 function(d){} 和 +d.xxx 类型转换(数字字段缺失时易得 NaN);✅ 推荐用 parseFloat() 并配合 isNaN() 校验;
  • ❌ 不要将 map 声明为全局变量(违反封装原则且易引发冲突);✅ 通过参数传递更清晰、可测试;
  • ✅ 使用 try/catch 捕获 CSV 加载异常(如 404、格式错误),提升应用鲁棒性;
  • ✅ 优先用 csvData.map() 批量赋值,比手动 for 循环更简洁、不易越界。

遵循以上模式,即可彻底解决“全局数组在异步加载后仍为 undefined”的问题,确保地理数据与地图渲染的严格时序依赖。