JavaScript异步函数重复执行引发多文件下载异常求助
问题描述
CalculateMedia函数嵌套在calculate()函数内部,每次点击特定按钮会触发calculate()执行。当前遇到的问题是:每点击一次计算按钮,后续点击下载按钮时,会下载所有之前生成过的文件,而非仅下载最后一次计算生成的文件。
问题根源
每次调用calculate()都会执行CalculateMedia,而CalculateMedia内部会重复给downloadButton和downloadButtonES添加点击事件监听器。例如:
- 第一次点击计算按钮,给下载按钮绑定1次下载回调;
- 第二次点击计算按钮,又绑定1次回调,此时下载按钮上有2个监听器;
- 点击下载时,所有绑定的回调都会触发,导致生成并下载多个历史文件。
解决方案
方案1:分离计算与下载逻辑(推荐)
将下载按钮的事件监听逻辑移到CalculateMedia外部,只绑定一次。在CalculateMedia中仅计算并保存最新的表单数据,下载时直接使用最新数据生成文件。
修改后的代码示例:
// 存储最新计算的数据 let latestCalculationData = {}; function calculate() { CalculateMedia(); } function CalculateMedia() { // 原有获取表单值、计算逻辑保持不变 let TrienalHolder = document.getElementById('trienal'); let trienal = (TrienalHolder.options[TrienalHolder.selectedIndex]).textContent; let BienalIHolder = document.getElementById('bienal_i'); let bienal_i = (BienalIHolder.options[BienalIHolder.selectedIndex]).textContent; let BienalIIHolder = document.getElementById('bienal_ii'); let bienal_ii = (BienalIIHolder.options[BienalIIHolder.selectedIndex]).textContent; let pt10 = document.getElementById("pt_10").value; let pt11 = document.getElementById("pt_11").value; let pt12 = document.getElementById("pt_12").value; let le10 = document.getElementById("le_10").value; let le11 = document.getElementById("le_11").value; let fil10 = document.getElementById("fil_10").value; let fil11 = document.getElementById("fil_11").value; let trienal10 = document.getElementById("trienal_10").value; let trienal11 = document.getElementById("trienal_11").value; let trienal12 = document.getElementById("trienal_12").value; let bienali10 = document.getElementById("bienal_i_10").value; let bienali11 = document.getElementById("bienal_i_11").value; let bienalii10 = document.getElementById("bienal_ii_10").value; let bienalii11 = document.getElementById("bienal_ii_11").value; let edf10 = document.getElementById("edf_10").value; let edf11 = document.getElementById("edf_11").value; let edf12 = document.getElementById("edf_12").value; let anuali = document.getElementById("anual_i").value; let anualii = document.getElementById("anual_ii").value; let Pi1 = document.getElementById("pi_pt").value; let Pi2 = document.getElementById("pi_le").value; let Pi3 = document.getElementById("pi_fil").value; let Pi4 = document.getElementById("pi_trienal").value; let Pi5 = document.getElementById("pi_bienal_i").value; let Pi6 = document.getElementById("pi_bienal_ii").value; let PPI = document.getElementById("peso_pi").value; //Média Final de cada disciplina let pt_final = (parseFloat(pt10) + parseFloat(pt11) + parseFloat(pt12)) / 3; let le_final = (parseFloat(le10) + parseFloat(le11)) / 2; let fil_final = (parseFloat(fil10) + parseFloat(fil11)) / 2; let trienal_final = (parseFloat(trienal10) + parseFloat(trienal11) + parseFloat(trienal12)) / 3; let bienal_i_final = (parseFloat(bienali10) + parseFloat(bienali11)) / 2; let bienal_ii_final = (parseFloat(bienalii10) + parseFloat(bienalii11)) / 2; let edf_final = (parseFloat(edf10) + parseFloat(edf11) + parseFloat(edf12)) / 3; let anual_i_final = parseFloat(anuali); let anual_ii_final = parseFloat(anualii); let PiPt = (parseFloat(Pi1)) / 10; let PiLe = (parseFloat(Pi2)) / 10; let PiFil = (parseFloat(Pi3)) / 10; let PiTrienal = (parseFloat(Pi4)) / 10; let PiBienali = (parseFloat(Pi5)) / 10; let PiBienalii = (parseFloat(Pi6)) / 10; let PesoProvasIngresso = (parseFloat(PPI)) / 100; let PesoMediaFinal = 1 - PesoProvasIngresso; let MediaFinalSecundario = ((pt_final + le_final + fil_final + trienal_final + bienal_i_final + bienal_ii_final + edf_final + anual_i_final + anual_ii_final) / 9).toFixed(2); let piArray = [PiPt, PiLe, PiFil, PiTrienal, PiBienali, PiBienalii]; let validPiArray = piArray.filter(function(value) { return !isNaN(value); }); let MediaProvasIngresso = validPiArray.length > 0 ? validPiArray.reduce(function(acc, value) { return acc + value; }, 0) / validPiArray.length : 0; let MediaFinalAcessoES = ((MediaFinalSecundario * PesoMediaFinal) + (MediaProvasIngresso * PesoProvasIngresso)).toFixed(2); let checkboxes = document.querySelectorAll("[id^='check_']"); let atLeastOneChecked = false; for (let i = 0; i < checkboxes.length; i++) { if (checkboxes[i].checked) { atLeastOneChecked = true; break; } } if (atLeastOneChecked) { document.getElementById("media-secundarioES").textContent = `${MediaFinalSecundario}`; document.getElementById("media-ES").textContent = `${MediaFinalAcessoES}`; $('#notafinalES').modal('show'); } else { document.getElementById("media-secundario").textContent = `${MediaFinalSecundario}`; $('#notafinal').modal('show'); } // 将最新计算结果存储到全局变量 latestCalculationData = { trienal, bienal_i, bienal_ii, pt10, pt11, pt12, le10, le11, fil10, fil11, trienal10, trienal11, trienal12, bienali10, bienali11, bienalii10, bienalii11, edf10, edf11, edf12, anuali, anualii, Pi1, Pi2, Pi3, Pi4, Pi5, Pi6, PPI, MediaFinalSecundario, MediaFinalAcessoES }; } // 独立的Excel生成下载函数 async function generateAndDownloadExcel(fillB19 = true) { const { trienal, bienal_i, bienal_ii, pt10, pt11, pt12, le10, le11, fil10, fil11, trienal10, trienal11, trienal12, bienali10, bienali11, bienalii10, bienalii11, edf10, edf11, edf12, anuali, anualii, Pi1, Pi2, Pi3, Pi4, Pi5, Pi6, PPI, MediaFinalSecundario, MediaFinalAcessoES } = latestCalculationData; const workbook = new ExcelJS.Workbook(); const response = await fetch('ExcelTemplate.xlsx'); const arrayBuffer = await response.arrayBuffer(); await workbook.xlsx.load(arrayBuffer); const sheet = workbook.getWorksheet(1); // 更新单元格值 sheet.getCell('B6').value = pt10; sheet.getCell('C6').value = pt11; sheet.getCell('D6').value = pt12; sheet.getCell('B7').value = le10; sheet.getCell('C7').value = le11; sheet.getCell('B8').value = fil10; sheet.getCell('C8').value = fil11; sheet.getCell('B9').value = trienal10; sheet.getCell('C9').value = trienal11; sheet.getCell('D9').value = trienal12; sheet.getCell('B10').value = bienali10; sheet.getCell('C10').value = bienali11; sheet.getCell('B11').value = bienalii10; sheet.getCell('C11').value = bienalii11; sheet.getCell('B12').value = edf10; sheet.getCell('C12').value = edf11; sheet.getCell('D12').value = edf12; sheet.getCell('D13').value = anuali; sheet.getCell('D14').value = anualii; sheet.getCell('E6').value = Pi1; sheet.getCell('E7').value = Pi2; sheet.getCell('E8').value = Pi3; sheet.getCell('E9').value = Pi4; sheet.getCell('E10').value = Pi5; sheet.getCell('E11').value = Pi6; sheet.getCell('B16').value = PPI; sheet.getCell('B18').value = MediaFinalSecundario; if (fillB19) { sheet.getCell('B19').value = MediaFinalAcessoES; } sheet.getCell('A9').value = trienal; sheet.getCell('A10').value = bienal_i; sheet.getCell('A11').value = bienal_ii; // 生成并下载文件 const buffer = await workbook.xlsx.writeBuffer(); const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); let currentDate = new Date().toLocaleDateString().replaceAll('/', '-'); let fileName = `MediaCalculada[${currentDate}].xlsx`; a.href = url; a.download = fileName; a.click(); setTimeout(() => URL.revokeObjectURL(url), 1000); } // 页面加载完成后只绑定一次下载事件 document.addEventListener('DOMContentLoaded', () => { const downloadButton = document.getElementById('downloadButton'); const downloadButtonES = document.getElementById('downloadButtonES'); downloadButton?.addEventListener('click', () => { generateAndDownloadExcel(false); }); downloadButtonES?.addEventListener('click', () => { generateAndDownloadExcel(); }); });
方案2:绑定新监听器前移除旧监听器
如果必须在CalculateMedia内部绑定事件,可以通过保存回调引用的方式,每次绑定前移除旧监听器:
// 保存回调引用,用于移除旧监听器 let downloadBtnCallback, downloadBtnESCallback; function CalculateMedia() { // 原有计算逻辑... // 先移除旧的监听器 const downloadButton = document.getElementById('downloadButton'); const downloadButtonES = document.getElementById('downloadButtonES'); if (downloadBtnCallback && downloadButton) { downloadButton.removeEventListener('click', downloadBtnCallback); } if (downloadBtnESCallback && downloadButtonES) { downloadButtonES.removeEventListener('click', downloadBtnESCallback); } // 定义新的回调并绑定 downloadBtnCallback = () => generateAndDownloadExcel(false); downloadBtnESCallback = () => generateAndDownloadExcel(); downloadButton?.addEventListener('click', downloadBtnCallback); downloadButtonES?.addEventListener('click', downloadBtnESCallback); // 原有generateAndDownloadExcel函数保持不变... }
说明
方案1更推荐,它将计算逻辑与下载逻辑分离,彻底避免了重复绑定事件的问题,同时代码结构更清晰,能确保下载时始终使用最新的计算数据。
内容的提问来源于stack exchange,提问作者user20733481
相关产品推荐
相关产品推荐

