You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

仅在循环中调用时XMLHttpRequest返回null的问题排查

问题分析与解决方案

这是异步请求开发里很常见的执行顺序坑,我来帮你拆解问题并给出修复方案:

核心问题:异步代码的执行时机错配

你现在的代码里,table.appendRows(tableData)和doneCallback()是在发送请求后立刻执行的,但request.onload是异步回调——也就是说,请求还没拿到数据,这两行就已经跑完了,此时tableData还是空数组,自然会出现响应为null的情况。

另外,如果你后续要加入for循环,用var x还会触发变量作用域提升的问题:所有异步回调都会共享同一个x变量,最后所有请求都会用循环结束后的x值(也就是2022),完全达不到迭代的效果。

分步修复方案

1. 先搞定单个请求的异步逻辑

把table.appendRows和doneCallback()移到request.onload回调内部,确保只有当请求成功返回数据后,才把数据传给Tableau:

myConnector.getData = function (table, doneCallback) {
    var tableData = []; // 用var声明避免全局变量污染
    var requestURL = "https://api.exchangerate.host/latest";
    var request = new XMLHttpRequest();
    request.open("GET", requestURL);
    request.responseType = "json";
    request.send();
    
    request.onload = function (resp) {
        var response = request.response;
        console.log(response);
        // 增加空响应判断,避免后续代码崩溃
        if (!response) {
            console.error("请求返回空数据");
            doneCallback();
            return;
        }
        var feat = response.rates;
        // 用Object.entries同时获取键值对,比分开取keys/values更高效
        var currencyEntries = Object.entries(feat);
        for (var [currencyAbr, currencyPrice] of currencyEntries) {
            tableData.push({
                Currency: currencyAbr,
                Price: currencyPrice,
                Base: response.base,
                // Year: x, 后续循环时启用
            });
        }
        // 数据处理完成后再传给Tableau
        table.appendRows(tableData);
        doneCallback();
    };

    // 增加请求错误处理,避免请求失败时整个连接器卡住
    request.onerror = function() {
        console.error("网络请求失败");
        doneCallback();
    };
};

2. 加入循环并解决闭包陷阱

当你要加入1999到2021的循环时,把var x改成let x(let有块级作用域,每次循环都会创建独立的x变量),同时用Promise封装请求,可以更优雅地处理多个异步请求,确保所有请求完成后再调用doneCallback():

// 先封装一个Promise版的请求函数,方便批量调用
function fetchExchangeRates(year) {
    return new Promise((resolve, reject) => {
        // 注意:api.exchangerate.host支持按日期查询,这里用每年1月1日的汇率
        var requestURL = `https://api.exchangerate.host/latest?date=${year}-01-01`;
        var request = new XMLHttpRequest();
        request.open("GET", requestURL);
        request.responseType = "json";
        request.onload = () => {
            if (request.status >= 200 && request.status < 300) {
                resolve({...request.response, year}); // 把年份和响应数据一起返回
            } else {
                reject(new Error(`请求失败,状态码:${request.status}`));
            }
        };
        request.onerror = () => reject(new Error("网络请求出错"));
        request.send();
    });
}

myConnector.getData = function (table, doneCallback) {
    var tableData = [];
    var requests = [];
    
    // 生成所有年份的请求Promise
    for (let x = 1999; x < 2022; x++) {
        requests.push(fetchExchangeRates(x));
    }

    // 等待所有请求完成后统一处理数据
    Promise.all(requests)
        .then(responses => {
            responses.forEach(resp => {
                var feat = resp.rates;
                var currencyEntries = Object.entries(feat);
                for (var [currencyAbr, currencyPrice] of currencyEntries) {
                    tableData.push({
                        Currency: currencyAbr,
                        Price: currencyPrice,
                        Base: resp.base,
                        Year: resp.year
                    });
                }
            });
            table.appendRows(tableData);
            doneCallback();
        })
        .catch(err => {
            console.error("批量请求出错:", err);
            doneCallback(); // 即使出错也要调用doneCallback,避免Tableau卡住
        });
};

额外提示

  • 用Object.entries()代替分别取keys和values,代码更简洁高效。
  • 一定要加错误处理逻辑,避免请求失败时整个连接器无响应。
  • 注意API的请求频率限制,如果一次性发23个请求,可能会被限流,必要时可以用setTimeout分批发送请求。

内容的提问来源于stack exchange,提问作者Dan_68

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 19:57:31