表格里的公式不一定只做本地计算。它可能请求服务器价格、读取库存、计算实时汇率,或者执行耗时任务。问题是,当一个工作表里散落着很多异步函数时,用户需要一个明确的"刷新一下"入口。一键触发工作表中所有异步函数 这个 demo 的思路很巧:让所有异步公式都引用同一个触发单元格,点击按钮时改变这个单元格的值,从而让依赖它的异步函数一起重算。
为什么这个细节重要
异步函数的用户体验难点,不在于"能不能算",而在于"什么时候算、怎么让用户知道"。如果每个公式各自刷新,页面会显得不可控;如果刷新入口太隐蔽,用户又不知道数据是否最新。把异步公式挂到共享依赖上,就像给工作表放了一个总开关。
对产品经理来说,这个按钮可以命名为"刷新价格""重新计算风险""同步最新库存";对开发者来说,它避免了逐个遍历公式再手动执行的复杂度,仍然沿用 SpreadJS 计算引擎的依赖关系。
更重要的是,这个方案让刷新行为可解释。用户点击按钮,表格出现 Loading...,随后结果更新,过程符合他们对在线数据的直觉。
SpreadJS 的实现思路
demo 定义了两个异步函数 MyFUN_1 和 MyFUN_2,都继承 GC.Spread.CalcEngine.Functions.AsyncFunction。defaultValue() 返回 Loading...,表示异步结果回来前的显示;evaluateAsync(context) 里通过 context.setAsyncResult 写回结果;evaluateMode() 返回 AsyncFunctionEvaluateMode.onRecalculation,表示在重新计算时执行。
JavaScript
var MyFun1 = function() {};
MyFun1.prototype =
new GC.Spread.CalcEngine.Functions.AsyncFunction("MyFUN_1", 1, 255);
MyFun1.prototype.defaultValue = function() {
return "Loading...";
};
MyFun1.prototype.evaluateAsync = function(context) {
context.setAsyncResult(Math.random());
};
MyFun1.prototype.evaluateMode = function() {
return GC.Spread.CalcEngine.Functions.AsyncFunctionEvaluateMode.onRecalculation;
};
函数注册到工作表后,B2:B11 交替写入 MyFUN_1(A1) 和 MyFUN_2(A1)。A1 本身不承载业务结果,只作为触发值。按钮点击时,代码把 A1 的布尔值取反。
JavaScript
sheet.addCustomFunction(new MyFun1());
sheet.addCustomFunction(new MyFun2());
sheet.setFormula(1, 1, "MyFUN_1(A1)");
sheet.setFormula(2, 1, "MyFUN_2(A1)");
$("#trigger").click(function() {
sheet.setValue(0, 0, !sheet.getValue(0, 0));
});
可以扩展到哪些场景
这个模式适合刷新实时价格、同步外部系统状态、批量更新接口返回值、重新计算风控分数、刷新设备监控指标。实际项目里,Math.random() 会换成 API 请求、缓存读取或异步计算;defaultValue 也可以换成"加载中""等待刷新"或空白占位。
需要注意,异步函数应处理失败、超时和并发。如果用户连续点击刷新按钮,最好有节流、取消或版本判断,避免旧请求结果覆盖新结果。
如果异步结果会影响关键决策,还应在表格上显示最后刷新时间,让用户知道当前数据的新鲜度。
给开发者的关键词
SpreadJS、GC.Spread.CalcEngine.Functions.AsyncFunction、defaultValue、evaluateAsync、setAsyncResult、evaluateMode、AsyncFunctionEvaluateMode.onRecalculation、addCustomFunction、异步公式刷新、JavaScript 电子表格控件。
小结
这个 demo 展示的不是复杂算法,而是一个好用的刷新模型:把多个异步公式组织到同一个依赖点下,让用户的一次点击自然触发整片结果更新。SpreadJS 的公式依赖机制因此可以和异步业务数据很好地衔接。