简体   繁体   中英

Communicate from server to client side in Google Apps script

I am trying to write a Google Apps script which has a client and server side component. The client side component displays a progress bar. The client calls server side functions (which are called asynchronously), whose progress has to be shown in the client side progress-bar. Now, what I want is to be able to update the client side progress bar based on feedback from the server side functions. Is this possible?

The complexity is created due the the fact that JS makes the server-side calls asynchronously and hence I cannot really have a loop on the client side calling the functions and updating the progress bar.

I could of course split up the execution of the server side function in multiple steps, call one by one from the client side, each time updating the status bar. But I'm wondering if there's a better solution. Is there a way to call a client side function from the server side, and have that update the progress bar based on the argument passed? Or is there a way to access the client side progress-bar object from server side and modify it?

The way I've handled this is to have a middleman (giving a shout out now to Romain Vialard for the idea) handle the progress: Firebase

The HTML/client side can connect to your Firebase account (they're free!) and "watch" for changes.

The client side code can update the database as it progresses through the code - those changes are immediately fed back to the HTML page via Firebase. With that, you can update a progress bar.

Romain has a small example/description here

The code I use:

//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>";
  }
});

On the client side, I use the Firebase library to update the progress:

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

Rather than tying the work requests and progress updating together, I recommend you separate those two concerns.

On the server side, functions that are performing work at the request of the client should update a status store; this could be a ScriptProperty, for example. The work functions don't need to respond to the client until they have completed their work. The server should also have a function that can be called by the client to simply report the current progress.

When the client first calls the server to request work, it should also call the progress reporter. (Presumably, the first call will get a result of 0% .) The onSuccess handler for the status call can update whatever visual you're using to express progress, then call the server's progress reporter again, with itself as the success handler. This should be done with a delay, of course.

When progress reaches 100% , or the work is completed, the client's progress checker can be shut down.

Building on Jens' approach, you can use the CacheService as your data proxy, instead of an external service. The way that I've approached this is to have my "server" application generate an interim cache key which it returns to the "client" application's success callback. The client application then polls this cache key at an interval to see if a result has been returned into the cache by the server application.

The server application returns an interim cache key and contains some helper functions to simplify checking this on the client-side:

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);
}

Note that by default getCacheKey doesn't return. This is so that google.script.run's successHandler doesn't get invoked until the cache entry returns non-null.

In the client application (in which I'm using Angular), you call off to the asynchronous operation in the server, and wait for its result:

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();

Using this approach you could also report progress, and only cancel your check after the progress reaches 100%. You'd want to eliminate the interval expiry in that case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM