dexteryao 发表于 2021-4-25 10:56:49

异步函数请求堆栈-解决页面多并发问题

本帖最后由 dexteryao 于 2021-4-25 10:56 编辑

SpreadJS提供了异步函数的功能,可以让函数通过调用API异步获取数据。但是当页面上使用的异步函数较多,刷新计算时会同时发生大量的网络请求,不仅给服务器造成压力,也会由于异步函数的同时更新造成页面的频繁刷新,影响用户体验。

为了解决这个问题,我们可以采用请求堆栈的方式,收集函数请求,统一发送网络请求并一次更新。
具体更新时机机制可以根据我们业务需求决定,下面以定时请求为例说明具体实现。
当第一个请求发生1秒后,发起一次网络请求。1秒内有其他请求进入就收集进堆栈,后续请求放入下一个堆栈,以此类推。

下面代码是一个普通异步函数GETNUMBERFROMSERVER的实现,调用getData接口(模拟接口直接返回arg1数据,未做其他处理),传递参数,获取显示内容并展示在单元格。
    var GetNumberFromServer = function () {
    };
    GetNumberFromServer.prototype = new GC.Spread.CalcEngine.Functions.AsyncFunction("GETNUMBERFROMSERVER", 1, 2);
    GetNumberFromServer.prototype.evaluate = function (context, arg1, arg2) {
    fetch("/spread/getData?data="+arg1)
    .then(function(response) {
      return response.text();
    })
    .then(function(text) {
      context.setAsyncResult(text);
    });
    };
    GC.Spread.CalcEngine.Functions.defineGlobalCustomFunction("GETNUMBERFROMSERVER", new GetNumberFromServer());

为了减少请求,我们使用一个缓存对象存放请求数据,定时调用接口处理
let callStack = {}; //收集请求数据
let callingStack = {}; //缓存正在请求中的数据信息
let callStackCount = 0; //请求数量,当作请求ID,用于区分请求内容
let timingId = 0; //用于判断当前是否有定时器等待请求中
定义请求方法,代替再函数中直接调用API接口

// data 请求数据
// context 异步函数context, 网络请求结束后回调时使用
// callback 回调函数
function stackCall(data, context, callback){
    let id = callStackCount++;
    callStack = {};
    callStack.data = data;
    callStack.context = context;
    callStack.callback = callback;

    if(timingId === 0){ // 同时只有一个定时器
      timingId = setTimeout(function(){
            callingStack = callStack;
            callStack = {};

            let newData = "" //合并请求数据,根据实际业务情况整理
            for(let cId in callingStack){
                newData += (cId + "," + callingStack.data + ";");
            }

            // 发送请求,这里模拟数据,发送什么返回什么
            fetch("/spread/getData?data=" + newData)
                  .then(function(response) {
                  return response.text();
                  })
                  .then(function(text) {
                  let resData = newData.split(";");
                  let spread = designer.getWorkbook();
                  spread.suspendPaint(); //暂定页面绘制

                  //解析返回的数据
                  for(let resId in resData){
                        if(resData){
                            let ress = resData.split(",");
                            // 根据Id,获取函数的context,调用callback回调
                            callingStack].callback.call(null, callingStack].context, ress)
                        }
                  }
                  spread.resumePaint(); //重启统一绘制
                timingId = 0;
            });
      }, 1000)
    }
}
更新异步函数实现方式,函数中调用stackCall堆栈函数,批量调用成功后执行callback回调中的setAsyncResult方法。
GetNumberFromServer.prototype.evaluate = function (context, arg1, arg2) {
    stackCall(arg1, context, function(context, text){
      context.setAsyncResult(text);
    })
    };



通过以上实现,当页面有大量异步请求时会统一处理,一次刷新。
另外还可以使用doNotRecalculateAfterLoad导入选项,在首次加载时不计算,使用json中原始值,以及calcOnDemand开启按需计算。
这两个option可以根据您的实际需求设置。
json.calcOnDemand = true;
spread.fromJSON(json, { doNotRecalculateAfterLoad: true });

athenadeveloper 发表于 2022-10-14 11:15:18

本帖最后由 athenadeveloper 于 2022-10-14 16:52 编辑

请教一下,代码中的处理返回数据段,如附件图。从代码来看是在拆分请求数据,此处是不需要拿到api的返回数据来做处理吗?

Ellia.Duan 发表于 2022-10-14 18:32:40

收到问题,这边调研下给您回复。

Ellia.Duan 发表于 2022-10-18 11:27:33

athenadeveloper 发表于 2022-10-14 11:15
请教一下,代码中的处理返回数据段,如附件图。从代码来看是在拆分请求数据,此处是不需要拿到api的返回数 ...


清酒℡ 发表于 2023-4-14 18:20:30

想请教一下版主 如果异步函数的调用这些代码是放在js文件里面的那这个方法里面的 designer有办法传进去吗用的技术栈是vue2+v16

Lynn.Dou 发表于 2023-4-17 18:13:33

文章中designer指的是designer(设计器)对象,您初始化deisgner获取对象后,就可以做后续的处理了。

清酒℡ 发表于 2023-6-12 17:20:33

Lynn.Dou 发表于 2023-4-17 18:13
文章中designer指的是designer(设计器)对象,您初始化deisgner获取对象后,就可以做后续的处理了。

再想请教下版主,如果定义了多个异步函数,sheet中也存在多个异步公式,触发计算时,这个函数应该如何改造,并且需要在所有异步公式计算完成后给用户一个计算完成的提示
页: [1]
查看完整版本: 异步函数请求堆栈-解决页面多并发问题