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

递归函数中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:55:55