Skip to content

异步执行JavaScript

概述

FBro支持异步执行JavaScript代码并获取返回值,支持多种数据类型的返回值处理。异步执行通过回调机制实现,不会阻塞主线程。

基础用法

1. 执行JavaScript并获取字符串返回值

csharp
// 创建一个JS回调类,这个类里面有个help变量用于等待数据返回
FBroSharpJsCallbackSyn callback = new FBroSharpJsCallbackSyn();

// 执行JavaScript代码
BrowserList.data[0].GetMainFrame().ExecuteJavaScriptToHasReturn(
    "function a(){var a=1.1;return a;};a();", 
    default, 
    default, 
    callback
);

if (callback.help != null && callback.help.IsValid)
{
    // 等待执行结果(超时时间10秒)
    callback.help.WaitEvent(10 * 1000);

    // 输出执行结果,这里的结果是回调中保存到help中的
    if (callback.help.HavStringData())
    {
        Console.WriteLine(callback.help.GetStringData());
    }

    // 清理全部数据(可选,会自动释放)
    callback.help.ClearData();
    Console.WriteLine(callback.help.HavStringData());
}

2. 执行JavaScript并获取布尔返回值

csharp
// 构建JavaScript代码
string jsCode = $@"
    function setInputValue(element, value) {{
        let lastValue = element.value;
        element.value = value;
        let event = new Event('input', {{ bubbles: true }});
        event.simulated = true;
        let tracker = element._valueTracker;
        if (tracker) {{
            tracker.setValue(lastValue);
        }}
        element.dispatchEvent(event);
        return true;  // 返回布尔值
    }}
    setInputValue(document.querySelector('#pg-title-input'), '{title.Replace("'", "\\'")}')
";

// 创建JS回调
FBroSharpJsCallbackSyn callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);

// 等待执行结果
if (callback.help != null && callback.help.IsValid)
{
    await Task.Run(() => callback.help.WaitEvent(10 * 1000));
    
    // 获取布尔结果
    if (callback.help.HavBoolData())
    {
        bool result = callback.help.GetBoolData();
        
        if (result)
        {
            OutputLog("商品标题设置成功");
            return true;
        }
    }
}

3. 执行JavaScript并获取对象返回值

当JavaScript返回对象时,FBro框架存在一些限制,需要特殊处理:

JavaScript对象处理的限制

问题描述: 对于JavaScript代码:(() => ({ key: "value", number: 42, a: 1.1 }))()

FBro会识别返回类型为VALUE_OBJECT,但无法直接访问对象的属性:

csharp
// 错误的尝试 - 这种方式无法获取对象内容
case FBroSharpJSRequestType.VALUE_OBJECT:
    // data.GetString() 只会返回 "object"
    // data.GetDictionary() 返回 null
    // data is IFBroSharpDictionaryValue 转换失败

测试结果分析

type: VALUE_OBJECT
data.GetType(): 5
data.GetString(): "object"
data.GetDictionary(): null
data.GetList(): null
data.IsValid: True

推荐解决方案:JSON序列化

方案1:使用JSON.stringify(推荐)

csharp
/// <summary>
/// 执行JavaScript并获取对象(通过JSON序列化)
/// </summary>
public static dynamic GetObjectFromJS(IFBroSharpBrowser browser, string jsObjectCode)
{
    // 包装JavaScript代码,使用JSON.stringify确保返回字符串
    string wrappedJsCode = $"JSON.stringify((() => {{ {jsObjectCode} }})());";
    
    FBroSharpJsCallbackSyn callback = new FBroSharpJsCallbackSyn();
    browser.GetMainFrame().ExecuteJavaScriptToHasReturn(wrappedJsCode, default, default, callback);
    
    if (callback.help != null && callback.help.IsValid)
    {
        callback.help.WaitEvent(10 * 1000);
        
        if (callback.help.HavStringData())
        {
            string jsonResult = callback.help.GetStringData();
            Console.WriteLine($"获取到JSON数据: {jsonResult}");
            
            try
            {
                // 使用Newtonsoft.Json解析
                return Newtonsoft.Json.JsonConvert.DeserializeObject(jsonResult);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"JSON解析失败: {ex.Message}");
                return null;
            }
        }
    }
    
    return null;
}

/// <summary>
/// 执行JavaScript并获取强类型对象
/// </summary>
public static T GetObjectFromJS<T>(IFBroSharpBrowser browser, string jsObjectCode)
{
    string wrappedJsCode = $"JSON.stringify((() => {{ {jsObjectCode} }})());";
    
    FBroSharpJsCallbackSyn callback = new FBroSharpJsCallbackSyn();
    browser.GetMainFrame().ExecuteJavaScriptToHasReturn(wrappedJsCode, default, default, callback);
    
    if (callback.help != null && callback.help.IsValid)
    {
        callback.help.WaitEvent(10 * 1000);
        
        if (callback.help.HavStringData())
        {
            string jsonResult = callback.help.GetStringData();
            
            try
            {
                return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(jsonResult);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"JSON解析为 {typeof(T).Name} 失败: {ex.Message}");
                return default(T);
            }
        }
    }
    
    return default(T);
}

使用示例

csharp
// 定义强类型类
public class TestObject
{
    public string key { get; set; }
    public int number { get; set; }
    public double a { get; set; }
}

// 使用方式1:获取动态对象
var result = GetObjectFromJS(browser, "return { key: 'value', number: 42, a: 1.1 }");
if (result != null)
{
    Console.WriteLine($"key: {result.key}");
    Console.WriteLine($"number: {result.number}");
    Console.WriteLine($"a: {result.a}");
}

// 使用方式2:获取强类型对象
var typedResult = GetObjectFromJS<TestObject>(browser, "return { key: 'value', number: 42, a: 1.1 }");
if (typedResult != null)
{
    Console.WriteLine($"key: {typedResult.key}");
    Console.WriteLine($"number: {typedResult.number}");
    Console.WriteLine($"a: {typedResult.a}");
}

方案2:直接在JavaScript中使用JSON.stringify

csharp
// 直接修改JavaScript代码
string jsCode = "JSON.stringify({ key: 'value', number: 42, a: 1.1 })";

FBroSharpJsCallbackSyn callback = new FBroSharpJsCallbackSyn();
browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);

if (callback.help != null && callback.help.IsValid)
{
    callback.help.WaitEvent(10 * 1000);
    
    if (callback.help.HavStringData())
    {
        string jsonString = callback.help.GetStringData();
        var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(jsonString);
        
        Console.WriteLine($"key: {obj.key}");
        Console.WriteLine($"number: {obj.number}");
        Console.WriteLine($"a: {obj.a}");
    }
}

回调类实现

FBroSharpJsCallbackSyn 类

这个类位于callback.cs文件中,用于处理JavaScript执行的异步回调:

csharp
public class FBroSharpJsCallbackSyn : FBroSharpJsCallback
{
    // 同步辅助类
    public FBroSharpSynHelp help;
    
    public FBroSharpJsCallbackSyn()
    {
        // 初始化help类
        help = new FBroSharpSynHelp();
    }

    public override void Callback(FBroSharpJSRequestType type, IFBroSharpValue data, string error)
    {
        // 将返回的数据保存在help中
        if (help != null && help.IsValid)
        {
            switch (type)
            {
                case FBroSharpJSRequestType.VALUE_ISVALID:
                    // 无效值
                    help.SetStringData(error);
                    break;
                    
                case FBroSharpJSRequestType.VALUE_NULL:
                    // 空值
                    help.SetStringData("无值");
                    break;
                    
                case FBroSharpJSRequestType.VALUE_INT:
                    // 整数值
                    help.SetIntData(data.GetInt());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_STRING:
                    // 字符串值
                    help.SetStringData(data.GetString());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_BOOL:
                    // 布尔值
                    help.SetBoolData(data.GetBool());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_DOUBLE:
                    // 双精度浮点数值
                    help.SetDoubleData(data.GetDouble());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_DATA_STRING:
                    // 日期时间字符串值
                    help.SetStringData(data.GetString());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_ARRAY:
                    // 数组值 - 注意:只能获取到字符串表示
                    help.SetStringData(data.GetString());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_ARRAY_BUFFER:
                    // 数组缓冲区值
                    help.SetStringData(data.GetString());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_FUNCTION:
                    // 函数值
                    help.SetStringData(data.GetString());
                    break;
                    
                case FBroSharpJSRequestType.VALUE_OBJECT:
                    // 对象值 - 注意:只能获取到"object"字符串
                    // 对于JavaScript原生对象,data.GetDictionary()返回null
                    // 建议使用JSON.stringify在JavaScript端转换
                    help.SetStringData(data.GetString());
                    break;
            }
            
            // 停止等待
            help.StopEvent();
        }
    }
}

支持的返回值类型

类型JavaScript类型C#获取方法说明限制
字符串stringGetStringData()文本数据
布尔值booleanGetBoolData()true/false
整数number (整数)GetIntData()32位整数
浮点数number (小数)GetDoubleData()双精度浮点数
数组ArrayGetStringData()只返回"[object Array]"无法获取内容,建议用JSON.stringify
对象ObjectGetStringData()只返回"object"⚠️ 无法获取属性,必须用JSON.stringify
函数FunctionGetStringData()函数的字符串表示无法执行
空值null/undefinedGetStringData()返回"无值"

重要提示

对象和数组的处理限制

FBro框架限制

  • IFBroSharpValue.GetDictionary()只适用于C#传递到JavaScript的字典对象
  • IFBroSharpValue.GetList()只适用于C#传递到JavaScript的列表对象
  • JavaScript原生对象和数组无法直接转换为C#的字典和列表

解决方案

  1. 对象:在JavaScript中使用JSON.stringify(object)
  2. 数组:在JavaScript中使用JSON.stringify(array)
  3. 复杂数据:统一使用JSON序列化/反序列化

为什么有GetDictionary()方法却无法使用?

IFBroSharpValue.GetDictionary()方法主要用于:

  1. C#→JS→C#:从C#传递字典到JavaScript,然后返回
  2. CEF内部对象:CEF框架内部创建的特殊对象
  3. FBro特定对象:FBro框架特定的对象类型

但不支持JavaScript原生的Object literal对象。

使用注意事项

1. 超时设置

csharp
// 设置等待超时时间(毫秒)
callback.help.WaitEvent(10 * 1000);  // 10秒超时

2. 数据检查

csharp
// 检查是否有对应类型的数据
if (callback.help.HavStringData())
{
    string result = callback.help.GetStringData();
}

if (callback.help.HavBoolData())
{
    bool result = callback.help.GetBoolData();
}

if (callback.help.HavIntData())
{
    int result = callback.help.GetIntData();
}

if (callback.help.HavDoubleData())
{
    double result = callback.help.GetDoubleData();
}

3. 资源清理

csharp
// 手动清理数据(通常不需要,会自动释放)
callback.help.ClearData();

4. 异步处理

csharp
// 在异步方法中使用
await Task.Run(() => callback.help.WaitEvent(10 * 1000));

最佳实践

  1. 错误处理:始终检查callback.help.IsValid和对应的数据类型
  2. 超时设置:根据JavaScript代码复杂度设置合理的超时时间
  3. 资源管理:对于频繁调用的场景,注意回调对象的复用
  4. 异步执行:在UI线程中使用时,考虑使用Task.Run避免阻塞
  5. 🔥 对象处理对于对象和数组,统一使用JSON序列化方案
  6. 类型检查:执行前先确认JavaScript返回的数据类型
  7. JSON库依赖:项目中添加Newtonsoft.Json NuGet包

完整示例

处理复杂JavaScript对象

csharp
/// <summary>
/// 完整的JavaScript对象获取示例
/// </summary>
public class JavaScriptObjectExample
{
    public static void GetComplexObject(IFBroSharpBrowser browser)
    {
        // 复杂JavaScript对象
        string jsCode = @"
            JSON.stringify({
                user: {
                    name: 'John',
                    age: 30,
                    address: {
                        city: 'Beijing',
                        country: 'China'
                    }
                },
                products: [
                    { id: 1, name: 'Product A', price: 100.5 },
                    { id: 2, name: 'Product B', price: 200.8 }
                ],
                timestamp: new Date().toISOString(),
                success: true
            })
        ";
        
        FBroSharpJsCallbackSyn callback = new FBroSharpJsCallbackSyn();
        browser.GetMainFrame().ExecuteJavaScriptToHasReturn(jsCode, default, default, callback);
        
        if (callback.help != null && callback.help.IsValid)
        {
            callback.help.WaitEvent(10 * 1000);
            
            if (callback.help.HavStringData())
            {
                string jsonResult = callback.help.GetStringData();
                
                try
                {
                    dynamic result = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonResult);
                    
                    Console.WriteLine($"用户名: {result.user.name}");
                    Console.WriteLine($"年龄: {result.user.age}");
                    Console.WriteLine($"城市: {result.user.address.city}");
                    Console.WriteLine($"产品数量: {result.products.Count}");
                    Console.WriteLine($"第一个产品: {result.products[0].name}");
                    Console.WriteLine($"时间戳: {result.timestamp}");
                    Console.WriteLine($"状态: {result.success}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"解析复杂对象失败: {ex.Message}");
                }
            }
        }
    }
}

应用示例

完整的应用示例

关于异步执行JavaScript获取元素坐标的完整应用示例和高级用法,请参考:

📄 异步执行JS获取元素坐标.md

该文档包含:

  • 🔧 通用方法封装:可复用的JavaScript执行和坐标获取方法
  • 📊 丰富数据结构:完整的位置、视口、元素信息
  • 🚀 智能交互功能:自动滚动、等待、批量处理
  • 🎯 实际应用场景:表单自动化、截图定位、布局测试
  • 性能优化技巧:批量查询、资源管理

如果文档对您有帮助,欢迎 请喝咖啡 ☕ | 软件发布 | 源码购买