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了
修复方法
核心逻辑很简单:
- 每个异步的Chrome API调用都单独包装成Promise,等回调执行完、拿到数据了再resolve
- 可以并行跑的任务(比如查序列号、查资产ID)丢给
Promise.all等待全部执行完成 - 流程按顺序走:先读存储配置 → 并行拉取所有配置要求的设备字段 → 拉取用户信息 → 所有数据凑齐了再调用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
相关产品推荐
相关产品推荐

