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

JS Promise结合switch、嵌套函数时如何确保异步完成后再执行回调

问题根源

你代码拿到空数组的核心原因是Promise resolve时机完全错误:
所有Chrome扩展提供的API都是异步回调风格,你在异步操作的回调还没执行、数据根本没来得及push进items的时候,就已经把Promise resolve、触发后续回调了,最后拿到的当然是空数组。
具体错误点有三个:

  • 最外层Promise的resolve(items)写在了chrome.storage.managed.get回调外面,JS刚发起存储查询请求,没等结果返回就直接把空数组resolve了
  • forEach循环里调用的getDeviceSerialNumber、getDeviceAssetId全是异步方法,你没等这些方法的回调执行完就往下走,就算把resolve挪到storage回调里,还是拿不到push进去的值
  • 第一个then回调里的chrome.identity.getProfileUserInfo也是异步的,你在then里同步return items,then链根本不会等用户信息查询完成,直接就走到最后调用callback了
修复方法

核心逻辑很简单:

  1. 每个异步的Chrome API调用都单独包装成Promise,等回调执行完、拿到数据了再resolve
  2. 可以并行跑的任务(比如查序列号、查资产ID)丢给Promise.all等待全部执行完成
  3. 流程按顺序走:先读存储配置 → 并行拉取所有配置要求的设备字段 → 拉取用户信息 → 所有数据凑齐了再调用callPHP

修复后的完整代码如下:

function callPHP(items) {
    console.log(items);
    console.log(items[0]);
    var xhr = new XMLHttpRequest();
    var data = items;
    var postUrl = *link to PHP file*;
    xhr.open('POST', postUrl, true);
    // 原代码漏了JSON请求头,PHP端可能无法正常解析POST数据
    xhr.setRequestHeader('Content-Type', 'application/json');
    xhr.send(JSON.stringify({
        items:data
    }));
    
    xhr.onreadystatechange = function() { 
        if (this.readyState === XMLHttpRequest.DONE) {
            if(this.status === 200) {
                console.log("Got response 200!", xhr.responseText);
            } else {
                console.error("Request failed, status:", this.status);
            }
        }
    };              
}

// 包装Chrome存储API为Promise
function getManagedStorage(keys) {
    return new Promise((resolve, reject) => {
        chrome.storage.managed.get(keys, (values) => {
            if(chrome.runtime.lastError) {
                return reject(chrome.runtime.lastError);
            }
            resolve(values);
        });
    });
}

// 包装获取序列号方法为Promise
function getDeviceSerial() {
    return new Promise((resolve) => {
        chrome.enterprise.deviceAttributes.getDeviceSerialNumber((sn) => {
            addToTable({'Serial Number': sn});
            resolve({Serial: sn});
        });
    });
}

// 包装获取资产ID方法为Promise
function getDeviceAssetId() {
    return new Promise((resolve) => {
        chrome.enterprise.deviceAttributes.getDeviceAssetId((aid) => {
            addToTable({'Asset ID': aid});
            resolve({Asset: aid});
        });
    });
}

// 包装获取用户信息方法为Promise
function getProfileUser() {
    return new Promise((resolve, reject) => {
        chrome.identity.getProfileUserInfo({'accountStatus': 'ANY'}, (info) => {
            if(chrome.runtime.lastError) {
                return reject(chrome.runtime.lastError);
            }
            addToTable({'User': info.email});
            resolve({User: info.email});
        });
    });
}

async function getUserInfo() {
    // 第一步:读取存储配置
    const values = await getManagedStorage(['primary_domain', 'fields', 'remote_url']);
    let fields = values.fields;
    if (typeof fields == 'undefined') {
        fields = 'annotatedAssetId,annotatedLocation,serialNumber';
    }
    fields = fields.split(',');
    
    const tasks = [];
    const remote_data = [];
    // 第二步:根据配置收集所有需要并行执行的异步任务
    fields.forEach(function(item) {
        switch (item) {
            case 'serialNumber':
                tasks.push(getDeviceSerial());
                break;
            case 'annotatedAssetId':
                tasks.push(getDeviceAssetId());
                break;
            default:
                remote_data.push(item);
        }
    });
    // 等待所有设备字段拉取完成
    const deviceItems = await Promise.all(tasks);
    // 第三步:拉取用户信息
    const userItem = await getProfileUser();
    // 拼接最终结果
    const items = [...deviceItems, userItem];
    // 有remote_data处理逻辑可以在这里追加
    return items;
}

document.onreadystatechange = function () {
    if (document.readyState === "interactive") {
        getUserInfo()
            .then(items => callPHP(items))
            .catch(err => console.error("Get info failed:", err));
    }
};
补充说明
  • 上面代码用了async/await语法,这是Promise的官方语法糖,比链式写then更直观,如果你需要兼容非常老的运行环境,可以改回纯then链写法,核心逻辑完全一致
  • Promise.all专门用来处理多个并行异步任务的等待逻辑,只有传入的所有Promise都执行完成,才会把所有结果按顺序拼成数组返回,非常适合你这种多个独立接口同时拉取、全部拉完再走下一步的场景
  • 所有Chrome API的包装都加了runtime.lastError判断,API报错的时候会直接进入catch分支,不会静默失败
  • 如果后续配置里加了其他需要异步拉取的字段,照着getDeviceSerial的写法包装成Promise、加入tasks数组即可,Promise.all会自动等待所有任务执行完成

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 11:27:17