如何在前端脚本中按需执行外部源的JS脚本?
问题描述
我正在开发一个从API获取数据的网站,该API以脚本形式提供,通常通过HTML的<script>标签引入:
<script type="text/javascript" src="https://www.fxblue.com/users/dynascalp_demo/overviewscript"></script>
该脚本会创建document.MTIntelligenceAccounts数组并推入所需数据,示例如下:
if (!document.MTIntelligenceAccounts) document.MTIntelligenceAccounts = new Array(); document.MTIntelligenceAccounts.push({ "userid": "dynascalp_demo","balance": 9275.95,"equity": 9275.95,"closedProfit": -724.05,"floatingProfit": 0,"freeMargin": 9275.95,"totalDeposits": 10000,"totalWithdrawals": 0,"totalBankedGrowth": -7.24,"monthlyBankedGrowth": -0.67,"weeklyBankedGrowth": -0.16,"dailyBankedGrowth": -0.03,"bankedProfitFactor": 0.66,"deepestValleyCash": -819.04,"deepestValleyPercent": -8.19,"troughInBalance": 9175.79,"peakInBalance": 10020.11,"historyLengthDays": 331,"averageTradeDurationHours": 2.17,"worstDayPercentage": -1.44,"worstWeekPercentage": -2.32,"worstMonthPercentage": -4.31,"tradesPerDay": 2.5,"totalClosedPositions": 589,"totalOpenPositions": 0,"bankedWinningTrades": 382,"bankedLosingTrades": 207,"bankedBreakEvenTrades": 0,"bankedWinPips": 1486.3,"bankedLossPips": -1604.6,"initialDeposit": 10000,"totalBankedPips":-118.3,"totalOpenPips":0,"peakPercentageLossFromOutset": -8.24,"riskReturnRatio": -1.21,"openAndPendingOrders": []});
我的需求是在前端脚本中按需执行该外部脚本:先查询数据库,若数据已存在且未过期(过期指超过1天未更新),则通过Ajax从服务器获取JSON数据;若数据不存在或已过期,则调用该API获取数据并更新数据库。由于后端不支持Node.js,所有逻辑需在前端实现。
目前遇到的问题:
- 使用
Fetch()请求该脚本URL会抛出“Unexpected end of input”错误,请求失败。 - 尝试动态创建
<script>标签的方式,但代码执行后无法正确获取数据并展示,示例代码如下:
示例JS代码
function fxBlue_retrieveAPI() { document.MTIntelligenceAccounts = new Array(); const url = "https://www.fxblue.com/users/dynascalp_demo/overviewscript"; //var fxblue_API_Names = ["dynascalp_demo", "fxprogoldrobot", "fxprosilverrobot", "forex_gump_ea"]; var varNames = ["totalDeposits", "balance", "totalBankedGrowth", "monthlyBankedGrowth", "deepestValleyPercent", "historyLengthDays"]; var experts = []; var s = document.createElement("script"); s.setAttribute("type", "text/javascript"); s.setAttribute("src", url); document.body.appendChild(s); for (var i = 0; i < document.MTIntelligenceAccounts.length; i++) { experts.push({ name: document.MTIntelligenceAccounts[i].userid, id: i }); if (document.getElementById(experts[i].name + varNames[0])) { document.getElementById(experts[i].name + varNames[0]).innerHTML = document.MTIntelligenceAccounts[experts[i].id].totalDeposits; } if (document.getElementById(experts[i].name + varNames[1])) { document.getElementById(experts[i].name + varNames[1]).innerHTML = document.MTIntelligenceAccounts[experts[i].id].balance; } if (document.getElementById(experts[i].name + varNames[2])) { document.getElementById(experts[i].name + varNames[2]).innerHTML = document.MTIntelligenceAccounts[experts[i].id].totalBankedGrowth + "%" }; if (document.getElementById(experts[i].name + varNames[3])) { document.getElementById(experts[i].name + varNames[3]).innerHTML = document.MTIntelligenceAccounts[experts[i].id].monthlyBankedGrowth }; if (document.getElementById(experts[i].name + varNames[4])) { document.getElementById(experts[i].name + varNames[4]).innerHTML = document.MTIntelligenceAccounts[experts[i].id].deepestValleyPercent + "%" }; if (document.getElementById(experts[i].name + varNames[5])) { document.getElementById(experts[i].name + varNames[5]).innerHTML = document.MTIntelligenceAccounts[experts[i].id].historyLengthDays }; } }
示例HTML代码
<script type="text/javascript" src="/cdn-cgi/scripts/API/jquery-3.1.1.min.js" async></script> <script type="text/javascript" src="/cdn-cgi/scripts/API/test.js"></script> <body onload="fxBlue_retrieveAPI()"> <h3>Total banked growth data example</h3> <p id="dynascalp_demototalBankedGrowth"></p> </body>
请问该如何解决这个问题,实现按需执行外部JS脚本并正确获取数据?
解决方案
1. 修复动态脚本加载的时序问题
你当前代码的核心问题是:动态创建的<script>标签是异步加载的,添加标签后立刻遍历document.MTIntelligenceAccounts时,脚本还未执行完成,数组仍为空。需监听脚本的load事件,确保脚本执行完毕后再处理数据:
function fxBlue_retrieveAPI() { document.MTIntelligenceAccounts = []; const url = "https://www.fxblue.com/users/dynascalp_demo/overviewscript"; const varNames = ["totalDeposits", "balance", "totalBankedGrowth", "monthlyBankedGrowth", "deepestValleyPercent", "historyLengthDays"]; const s = document.createElement("script"); s.type = "text/javascript"; s.src = url; // 脚本加载完成后执行数据处理逻辑 s.onload = function() { document.MTIntelligenceAccounts.forEach((account) => { const expertName = account.userid; varNames.forEach(varName => { const elem = document.getElementById(`${expertName}${varName}`); if (elem) { let value = account[varName]; // 为百分比字段添加%符号 if (["totalBankedGrowth", "deepestValleyPercent"].includes(varName)) { value += "%"; } elem.textContent = value; } }); }); }; // 处理脚本加载失败的情况 s.onerror = function() { console.error("FXBlue脚本加载失败"); }; document.body.appendChild(s); }
2. 实现前端数据缓存逻辑
利用localStorage实现前端缓存,判断数据是否过期,避免重复加载外部脚本:
// 获取缓存数据,判断是否过期 function getCachedFXBlueData() { const cached = localStorage.getItem("fxBlueData"); if (!cached) return null; const { data, timestamp } = JSON.parse(cached); // 1天=86400000毫秒 if (Date.now() - timestamp > 86400000) { localStorage.removeItem("fxBlueData"); return null; } return data; } // 保存数据到缓存 function saveFXBlueDataToCache(data) { localStorage.setItem("fxBlueData", JSON.stringify({ data, timestamp: Date.now() })); } // 整合缓存逻辑的主函数 function fetchFXBlueData() { const cachedData = getCachedFXBlueData(); if (cachedData) { // 使用缓存数据更新页面 updatePageWithFXBlueData(cachedData); return; } // 缓存不存在或过期,加载外部脚本 document.MTIntelligenceAccounts = []; const url = "https://www.fxblue.com/users/dynascalp_demo/overviewscript"; const s = document.createElement("script"); s.type = "text/javascript"; s.src = url; s.onload = function() { const data = document.MTIntelligenceAccounts; saveFXBlueDataToCache(data); updatePageWithFXBlueData(data); }; s.onerror = function() { console.error("FXBlue脚本加载失败"); }; document.body.appendChild(s); } // 抽离页面更新逻辑,复用缓存和新数据的更新操作 function updatePageWithFXBlueData(data) { const varNames = ["totalDeposits", "balance", "totalBankedGrowth", "monthlyBankedGrowth", "deepestValleyPercent", "historyLengthDays"]; data.forEach((account) => { const expertName = account.userid; varNames.forEach(varName => { const elem = document.getElementById(`${expertName}${varName}`); if (elem) { let value = account[varName]; if (["totalBankedGrowth", "deepestValleyPercent"].includes(varName)) { value += "%"; } elem.textContent = value; } }); }); }
3. 修改HTML调用逻辑
将页面加载事件改为调用整合后的主函数,同时可补充需要展示的字段元素:
<script type="text/javascript" src="/cdn-cgi/scripts/API/jquery-3.1.1.min.js"></script> <script type="text/javascript" src="/cdn-cgi/scripts/API/test.js"></script> <body onload="fetchFXBlueData()"> <h3>Total banked growth data example</h3> <p id="dynascalp_demototalBankedGrowth"></p> <p id="dynascalp_demobalance"></p> <p id="dynascalp_demototalDeposits"></p> </body>
关键说明
- 动态脚本必须等待
load事件触发后再处理数据,因为脚本加载和执行是异步的。 - 使用
localStorage存储缓存时,注意其5MB左右的容量限制,以及数据时效性的判断逻辑。 - 务必添加脚本加载失败的错误处理,避免页面无响应或静默报错。
内容的提问来源于stack exchange,提问作者Eduardo Meli
相关产品推荐
相关产品推荐

