递归函数中DB异步请求时序问题的优化方案咨询
解决嵌套函数解析中异步DB请求的同步阻塞问题
我写了一个JavaScript类,用来递归解析嵌套函数系统。其中DB函数需要通过AJAX POST请求调用PHP页面执行数据库查询,返回值后赋值给函数对象。
示例嵌套函数
IF([DB("client", 2, "dob") > DATE("2024-04-24")], "TRUE", CONCAT$("The report is due on ", DATE$(-4)))
当前问题
DB请求是异步操作,需要时间响应,但递归逻辑会在DB请求返回值之前,就执行DB()与DATE()的比较操作。目前设置async:false能正常运行(UI卡顿不明显),但想找到更优方案。试过检查子节点是否获取值再延迟执行,但效果和同步调用差不多。
优化方案
核心思路是将函数求值逻辑改为异步,用async/await处理所有依赖,既保证逻辑顺序,又不会阻塞UI线程。以下是修改后的完整实现:
class Function { constructor(definition) { this.definition = definition; this.cat = "not a function"; if (this.checkSyntax()) this.cat = "function"; // 构造函数仅做语法解析,求值逻辑移至evaluate方法 } checkSyntax() { if (this.definition.match(/\^/)) throw new Error("公式中不能包含'^'字符: " + this.definition); const regex = /^([A-Z\$]+)\((.*)\)$/; const match = this.definition.match(regex); if (match) { this.name = match[1]; if (!function_names[match[1]]) throw new Error(match[1] + "不是有效的函数名: " + this.definition); this.params = SpecialSplit(match[2]).map(param => { const trimmedParam = param.trim(); return trimmedParam ? new Function(trimmedParam) : null; }).filter(param => param !== null); const openParenthesesCount = (this.definition.match(/\(/g) || []).length; const closeParenthesesCount = (this.definition.match(/\)/g) || []).length; if (openParenthesesCount === closeParenthesesCount) return true; throw new Error("括号数量不匹配: " + this.definition); } else return false; } async evaluate() { if (this.cat !== "function") { if (this.definition.match(/^\[.*\]$/)) { this.cat = "[COMPARISON]"; const a = parseExpression(this.definition); const leftFunc = new Function(a.pre); const rightFunc = new Function(a.post); // 等待左右两边求值完成再进行比较 await leftFunc.evaluate(); await rightFunc.evaluate(); this.left = leftFunc; this.test = a.test; this.right = rightFunc; this.output = "[COMPARISON]"; if (this.left.output !== this.right.output && this.left.output !== "VARY" && this.right.output !== "VARY") throw new Error("无法比较'" + this.left.output + "'和'" + this.right.output + "': " + this.definition); else if (this.left.output === "DATE" || this.left.output === "VARY") this.value = eval(`new Date(${JSON.stringify(this.left.value)}) ${this.test} new Date(${JSON.stringify(this.right.value)})`); else if (this.left.output === "INTEGER" || this.left.output === "STRING") this.value = eval(`${JSON.stringify(this.left.value)} ${this.test} ${JSON.stringify(this.right.value)}`); else throw new Error(this.left.output + "类型的比较逻辑未实现: " + this.definition); this.eval = `${this.left.value} ${this.test} ${this.right.value}`; } else if (this.definition.match(/^"[^"]*"$/)) { this.cat = "STRING"; this.value = this.definition.replace(/"/g, ""); this.output = "STRING"; } else if (!isNaN(this.definition)) { this.cat = "INTEGER"; this.value = +this.definition; this.output = "INTEGER"; } else if (this.definition === "TRUE") { this.cat = "BOOLEAN"; this.value = true; this.output = "BOOLEAN"; } else if (this.definition === "FALSE") { this.cat = "BOOLEAN"; this.value = false; this.output = "BOOLEAN"; } else throw new Error("未知参数: " + this.definition); } else { // 并行求值所有参数,提升效率 await Promise.all(this.params.map(param => param.evaluate())); const param_string = this.params.map(element => element.cat === "function" ? element.output : element.cat).join(", "); let the_formula; let param_options = ""; function_names[this.name].forEach(element => { param_options += "<br>" + element["parameters"]; let testParts = element["parameters"].split(", "); if (element["parameters"] === "[STRING]") testParts = this.params.map(() => "STRING"); const conditionParts = param_string.split(", "); let match = true; if (testParts.length === conditionParts.length) { for (let i = 0; i < testParts.length; i++) { if (testParts[i] !== "VARY" && testParts[i] !== conditionParts[i]) { match = false; break; } } } else { match = false; } if (match) the_formula = element; }); if (!the_formula) throw new Error("函数'" + this.name + "'没有匹配'" + param_string + "'的定义<br><br>可选参数格式:<br>" + param_options); this.syntax = the_formula["name"] + "(" + the_formula["parameters"] + ")"; this.output = the_formula["response"]; switch (this.name) { case "DATE": { let date = new Date(); if (this.syntax === "DATE(INTEGER)") date.setDate(date.getDate() + this.params[0].value); if (this.syntax === "DATE(STRING)") date = new Date(this.params[0].value); this.value = date; break; } case "DATE$": { let date = new Date(); if (this.syntax === "DATE$(INTEGER)") date.setDate(date.getDate() + this.params[0].value); this.value = date.toLocaleDateString('en-CA').replace(/\//g, '-'); break; } case "IF": { this.value = this.params[0].value ? this.params[1].value : this.params[2].value; break; } case "CONCAT$": { this.value = concatenateValues(this.params); break; } case "DB": { // 改用异步AJAX,避免阻塞UI const response = await $.ajax({ url: 'includes/external_db.php', type: 'POST', data: { action: "get_column_value", table: this.params[0].value, id: this.params[1].value, col: this.params[2].value, debug: "false" }, async: true }); this.value = response; break; } } } } } class ExpressionPart { constructor(pre, test, post) { this.pre = pre.trim(); this.test = test.trim(); this.post = post.trim(); } } function parseExpression(input) { const match = input.match(/^\[\s*(.*?)([<>!=]=?)(.*)\]$/); if (match) { const pre = match[1]; const test = match[2]; const post = match[3]; return new ExpressionPart(pre, test, post); } else { return null; } } function SpecialSplit(paramString) { let current = ''; let depth = 0; for (const char of paramString) { if (char === '(') { depth++; } else if (char === ')') { depth--; } if (char === ',' && depth === 0) { current += '^'; } else { current += char; } } return current.trim().split('^'); } function concatenateValues(params) { let concatenatedValue = ""; for (const param of params) concatenatedValue += param.value; return concatenatedValue; }
使用示例
async function runFormula() { const formula = `IF([DB("client", 2, "dob") > DATE("2024-04-24")], "TRUE", CONCAT$("The report is due on ", DATE$(-4)))`; const func = new Function(formula); try { await func.evaluate(); console.log(func.value); // 获取最终计算结果 } catch (err) { console.error(err); } } runFormula();
优化说明
- 异步求值:所有函数的计算逻辑通过
evaluate()异步方法完成,用async/await保证依赖顺序,不会阻塞UI线程 - 并行处理:参数求值使用
Promise.all()并行执行,提升整体计算效率 - 消除同步阻塞:DB请求恢复异步模式,彻底解决UI卡顿隐患
- 逻辑分离:构造函数仅负责语法解析,求值逻辑独立成方法,代码结构更清晰
内容的提问来源于stack exchange,提问作者Brian
相关产品推荐
相关产品推荐

