异步执行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#获取方法 | 说明 | 限制 |
|---|---|---|---|---|
| 字符串 | string | GetStringData() | 文本数据 | 无 |
| 布尔值 | boolean | GetBoolData() | true/false | 无 |
| 整数 | number (整数) | GetIntData() | 32位整数 | 无 |
| 浮点数 | number (小数) | GetDoubleData() | 双精度浮点数 | 无 |
| 数组 | Array | GetStringData() | 只返回"[object Array]" | 无法获取内容,建议用JSON.stringify |
| 对象 | Object | GetStringData() | 只返回"object" | ⚠️ 无法获取属性,必须用JSON.stringify |
| 函数 | Function | GetStringData() | 函数的字符串表示 | 无法执行 |
| 空值 | null/undefined | GetStringData() | 返回"无值" | 无 |
重要提示
对象和数组的处理限制
FBro框架限制:
IFBroSharpValue.GetDictionary()只适用于C#传递到JavaScript的字典对象IFBroSharpValue.GetList()只适用于C#传递到JavaScript的列表对象- JavaScript原生对象和数组无法直接转换为C#的字典和列表
解决方案:
- 对象:在JavaScript中使用
JSON.stringify(object) - 数组:在JavaScript中使用
JSON.stringify(array) - 复杂数据:统一使用JSON序列化/反序列化
为什么有GetDictionary()方法却无法使用?
IFBroSharpValue.GetDictionary()方法主要用于:
- C#→JS→C#:从C#传递字典到JavaScript,然后返回
- CEF内部对象:CEF框架内部创建的特殊对象
- 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));最佳实践
- 错误处理:始终检查
callback.help.IsValid和对应的数据类型 - 超时设置:根据JavaScript代码复杂度设置合理的超时时间
- 资源管理:对于频繁调用的场景,注意回调对象的复用
- 异步执行:在UI线程中使用时,考虑使用
Task.Run避免阻塞 - 🔥 对象处理:对于对象和数组,统一使用JSON序列化方案
- 类型检查:执行前先确认JavaScript返回的数据类型
- JSON库依赖:项目中添加
Newtonsoft.JsonNuGet包
完整示例
处理复杂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获取元素坐标的完整应用示例和高级用法,请参考:
该文档包含:
- 🔧 通用方法封装:可复用的JavaScript执行和坐标获取方法
- 📊 丰富数据结构:完整的位置、视口、元素信息
- 🚀 智能交互功能:自动滚动、等待、批量处理
- 🎯 实际应用场景:表单自动化、截图定位、布局测试
- ⚡ 性能优化技巧:批量查询、资源管理