【问题标题】:Communicate from server to client side in Google Apps script在 Google Apps 脚本中从服务器到客户端通信
【发布时间】:2025-11-21 11:10:01
【问题描述】:

我正在尝试编写一个具有客户端和服务器端组件的 Google Apps 脚本。客户端组件显示一个进度条。客户端调用服务器端函数(异步调用),其进度必须显示在客户端进度条中。现在,我想要的是能够根据来自服务器端功能的反馈来更新客户端进度条。这可能吗?

复杂性是由于 JS 异步进行服务器端调用这一事实造成的,因此我不能在客户端真正有一个循环来调用函数并更新进度条。

我当然可以将服务器端函数的执行拆分为多个步骤,从客户端一个一个地调用,每次更新状态栏。但我想知道是否有更好的解决方案。有没有办法从服务器端调用客户端函数,并根据传递的参数更新进度条?或者有没有办法从服务器端访问客户端进度条对象并修改它?

【问题讨论】:

    标签: javascript asynchronous google-apps-script progress-bar


    【解决方案1】:

    我处理此问题的方式是让中间人(现在向 Romain Vialard 大声疾呼)处理进度:Firebase

    HTML/客户端可以连接到您的 Firebase 帐户(它们是免费的!)并“监视”更改。

    客户端代码可以在代码执行过程中更新数据库 - 这些更改会立即通过 Firebase 反馈到 HTML 页面。这样,您可以更新进度条。

    Romain 有一个小例子/描述here

    我使用的代码:

    //Connect to firebase
    var fb = new Firebase("https://YOUR_DATABASE.firebaseio.com/");
    //Grab the 'child' holding the progress info
    var ref = fb.child('Progress');
    //When the value changes
    ref.on("value", function(data) {
      if (data.val()) {
        var perc = data.val() * 100;
        document.getElementById("load").innerHTML = "<div class='determinate' style='width:" + perc + "%\'></div>";
      }
    });
    

    在客户端,我使用Firebase library更新进度:

    var fb = FirebaseApp.getDatabaseByUrl("https://YOUR_DATABASE..firebaseio.com/");
    var data = { "Progress": .25};
    fb.updateData("/",data);
    

    【讨论】:

    【解决方案2】:

    我建议您将这两个问题分开,而不是将工作请求和进度更新捆绑在一起。

    在服务器端,应客户端请求执行工作的函数应该更新状态存储;例如,这可能是一个 ScriptProperty。工作职能在完成工作之前不需要响应客户。服务器还应该有一个可以被客户端调用来简单地报告当前进度的函数。

    当客户端第一次调用服务器请求工作时,它也应该调用进度报告器。 (大概,第一次调用将得到0% 的结果。)状态调用的onSuccess 处理程序可以更新您用来表达进度的任何视觉效果,然后再次调用服务器的进度报告器,并将其本身作为成功处理程序。当然,这应该延迟完成。

    当进度达到100%,或者工作完成时,可以关闭客户端的进度检查器。

    【讨论】:

    • 这是对 GAS 的出色且“规范”的使用。我在整个内胎中找到的最好的!仅供参考,ScriptProperty 已被折旧并替换为Property Service
    【解决方案3】:

    基于 Jens 的方法,您可以使用 CacheService 作为您的数据代理,而不是外部服务。我解决这个问题的方法是让我的“服务器”应用程序生成一个临时缓存键,并将其返回给“客户端”应用程序的成功回调。然后,客户端应用程序会定期轮询此缓存键,以查看服务器应用程序是否已将结果返回到缓存中。

    服务器应用程序返回一个临时缓存键,并包含一些帮助函数以简化在客户端的检查:

    function someAsynchronousOperation() {
      var interimCacheKey = createInterimCacheKey();
    
      doSomethingComplicated(function(result) {
          setCacheKey(interimCacheKey, result);
      });
    
      return interimCacheKey;
    }
    
    function createInterimCacheKey() {
      return Utilities.getUuid();
    }
    
    function getCacheKey(cacheKey, returnEmpty) {
      var cache = CacheService.getUserCache();
      var result = cache.get(cacheKey);
    
      if(result !== null || returnEmpty) {
        return result;
      }
    }
    
    function setCacheKey(cacheKey, value) {
      var cache = CacheService.getUserCache();
    
      return cache.put(cacheKey, value);
    }
    

    请注意,默认情况下 getCacheKey 不会返回。这是为了让 google.script.run 的 successHandler 在缓存条目返回非 null 之前不会被调用。

    在客户端应用程序(我在其中使用 Angular)中,您调用服务器中的异步操作,并等待其结果:

    google.script.run.withSuccessHandler(function(interimCacheKey) {
      var interimCacheCheck = $interval(function() {
        google.script.run.withSuccessHandler(function(result) {
          $interval.cancel(interimCacheCheck);
          handleSomeAsynchronousOperation(result);
        }).getCacheKey(interimCacheKey, false);
      }, 1000, 600); // Check result once per second for 10 minutes
    }).someAsynchronousOperation();
    

    使用这种方法您还可以报告进度,并且只有在进度达到 100% 后才取消您的检查。在这种情况下,您需要消除间隔到期。

    【讨论】: