Using promises with apps script

Using promises rather than callbacks is a much cleaner way of dealing with asynchronous activities. Promises were introduced in EcmaScript 6, and of course are not implemented in Server side Apps Script. Since everything is synchronous there anyway, it's not too big of a deal.

However you can use Promises for client side code when using HtmlService - the IFrame mode allows it to run just fine. You just need to make sure you include a polyfill, in case it's running on an old browser. 

Using promises with google.script.run

If you use google.script.run, you'll know this is a way to make calls to server side functions. What's really good about this is that it's asynch, so this means you can make multiple calls simultaneously - a great way to make your web apps more responsive, but it's a bit messy since you have to have global functions to call, and you also have to deal with callbacks. 

But you can use promises instead - here's how. 

An example - this runs Server.getData (100);
Provoke.run ( 'Server', 'getData' , 100)
.then (function (result) {
   '//do something with the result
},
function (err) {
  //do something with the error
});


What's nice about this approach is that Provoke.run returns a promise, so you can use things like promise.all() to wait until a number of tasks have all completed - for example (a null namespace argument means its a global server side function)
Promise.all ( [
  Provoke.run('Server','getData',100) , 
  Provoke.run(null, 'findThis', 'abc',25), 
  Provoke.run ('Logger','log','something')]
)
.then (function(results) {
  // do something with all the results
});

Later on I'll show some example of some cool webapps using this approach.

The code

In your html file you'll need the polyfill in case it's running on old browsers
 <script src="//cdnjs.cloudflare.com/ajax/libs/es6-promise/3.2.1/es6-promise.min.js"></script>

In your client side code you'll need this provoke namespace
/**
 * @namespace Provoke
 * promise management for async calls
 */

var Provoke =(function (ns) {

  /**
  * run something asynchronously
  * @param {string} namespace the namespace (null for global)
  * @param {string} method the method or function to call
  * @param {...} the args
  * @return {Promise} a promise
  */
  ns.run = function (namespace,method) {
    
    // the args to the server function
    var runArgs = Array.prototype.slice.call(arguments).slice(2);
    console.log(runArgs);
    if (arguments.length<2) {
      throw new Error ('need at least a namespace and method');
    }

    // this will return a promise
    return new Promise(function ( resolve , reject ) {
      
      google.script.run
    
      .withFailureHandler (function(err) {
        reject (err);
      })
    
      .withSuccessHandler (function(result) {
        resolve (result);
      })
    
      .exposeRun (namespace,method,runArgs); 
    });
    
    
  };
  
  
  return ns;
  
})(Provoke || {});


and you'll also need this server side global function.
/**
* used to expose memebers of a namespace
* @param {string} namespace name
* @param {method} method name
*/
function exposeRun (namespace, method , argArray ) {
  var func = (namespace ? this[namespace][method] : this[method])
  if (argArray && argArray.length) {
    return func.apply(this,argArray);
  }
  else {
    return func();
  }
}

Whitelisting/blacklisting

If you want to make sure that only certain methods or namespaces are ever invoked from the client side with exposeRun, then you can do that like this example. For example my Apps only ever allow functions in the Server namespace to be provoked, so I have a slightly modified version of exposeRun
/**
* used to expose memebers of a namespace
* @param {string} namespace name
* @param {method} method name
*/
function exposeRun (namespace, method , argArray ) {
  if (namespace !== "Server") {
    throw new Error ('Tried to execute method ' + method + ' from prohibited namespace' + namespace)
  }
  var func = (namespace ? this[namespace][method] : this[method])
  if (argArray && argArray.length) {
    return func.apply(this,argArray);
  }
  else {
    return func();
  }
}
And that's it, you're using Promises to call Apps Script Server side functions!

For more like this, see Google Apps Scripts snippets. Why not join our forumfollow the blog or follow me on twitter to ensure you get updates when they are available. 

You want to learn Google Apps Script?

Learning Apps Script, (and transitioning from VBA) are covered comprehensively in my my book, Going Gas - from VBA to Apps script, available All formats are available now from O'Reilly,Amazon and all good bookshops. You can also read a preview on O'Reilly

If you prefer Video style learning I also have two courses available. also published by O'Reilly.
Google Apps Script for Developers and Google Apps Script for Beginners.






Comments