使用 Javascript async & await with google.script.url.getLocation

Using Javascript async & await with google.script.url.getLocation

我正在尝试重构 Google Apps Script 网络应用程序中的一些难看的代码,以便它使用 async / await.

它使用 google.script.url.getLocation 客户端,提取 URL 参数,然后将它们发送给其他异步函数。

一定有办法优雅地做到这一点。

var  doSomeAsyncShit =()=> {
    google.script.url.getLocation(function (location) {

      var rid = (location.parameter.rid) ? location.parameter.rid : defaultReportID;
      var uid = (location.parameter.uid) ? location.parameter.uid : defaultUserID;

      console.log (((location.parameter.rid) ? "Report #" : "Default Report ID #")+rid);
      console.log (((location.parameter.uid) ? "User #" : "Default User ID #" )+uid);

      google.script.run.withSuccessHandler(paintReport).returnJSON(rid);
      google.script.run.withSuccessHandler(getMyReportsList).listMyReports(uid);
    });

  }

  

  $(function () {

    doSomeAsyncShit();
}

由于 Promise 可以用自定义执行函数构造,您可以将 google.script.url 包装到其中,并在需要时解析或拒绝。如果您随后将其设为效用函数,请使用 await 等待其解析。

下面是一个灵活的小实用程序,用于制作 google.script.url async-friendly:

/**
 * @typedef {{
 *  hash : string,
 *  parameter : Object.<string, string>,
 *  parameters : Object.<string, string[]>
 * }} UrlLocationObject
 * 
 * @typedef {{
 *  callback : function (UrlLocationObject, ...any) : any,
 *  params : any[]
 * }} AsyncUrlOptions
 * 
 * @summary Promise-friendly google.script.url
 * @param {AsyncUrlOptions}
 * @returns {Promise}
 */
const asyncLocation = ({
    callback,
    params = [],
}) => {
    return new Promise((res, rej) => {
        google.script.url.getLocation((loc) => {
            try {
                const result = callback(loc, ...params);
                res(result);
            }
            catch(error) {
                rej(error);
            }
        });
    });
};

google.script.run也是如此:

/**
 * @typedef {{
 *  funcName : string,
 *  onFailure : function,
 *  onSuccess : function,
 *  params : array
 * }} AsyncOptions
 * 
 * @summary v2 of async-friendly google.script.run
 * @param {AsyncOptions}
 * @returns {Promise}
 */
const asyncGAPIv2 = ({
    funcName,
    onFailure = console.error,
    onSuccess,
    params = []
}) => {
    return new Promise((res, rej) => {
        google.script.run
            .withSuccessHandler(data => {
                typeof onSuccess === "function" && onSuccess(data);
                res(data);
            })
            .withFailureHandler(error => {
                typeof onFailure === "function" && onFailure(error);
                rej(error);
            })
        [funcName].apply(null, params);
    });
};

可以拦截对 google api 和直接 return Promise using Proxy 的请求。

脚本:

/**
 * Revives old client facing google api in apps script web applications
 * Directly returns promises for `google.scipt.run` and `google.script.url.getLocation`
 * @see 
 */
(function projectAdrenaline_google() {
  const lifeline = {
    funcList: [],
    excludeList: [
      'withSuccessHandler',
      'withFailureHandler',
      'withUserObject',
      'withLogger',
    ],
    get: function(target, prop, rec) {
      if (this.excludeList.includes(prop))
        //return (...rest) => new Proxy(Reflect.apply(target[prop], target, rest), trap);
        throw new TypeError(
          `${prop}: This method is deprecated in this custom api`
        );
      if (this.funcList.includes(prop))
        return (...rest) =>
          new Promise((res, rej) =>
            target
              .withSuccessHandler(res)
              .withFailureHandler(rej)
              [prop](...rest)
          );
      switch (prop) {
        case 'run':
          this.funcList = Object.keys(target.run);
          break;
        case 'getLocation':
          return () => new Promise(res => target[prop](res));
      }
      return new Proxy(Reflect.get(target, prop, rec), lifeline);
    },
  };
  //const superGoogle = new Proxy(google, trap);
  //OR overwrite currently loaded google object:
  google = new Proxy(google, lifeline);
})();

示例:

const doSomeAsyncStuff = async () => {
  const location = await google.script.url.getLocation();

  const rid = location.parameter.rid ? location.parameter.rid : defaultReportID;
  const uid = location.parameter.uid ? location.parameter.uid : defaultUserID;

  //promise
  google.script.run.returnJSON(rid).then(paintReport);
  //async-await
  const reportsList = await google.script.run.listMyReports(uid);
  getMyReportsList(reportsList);
};

或者,可以将函数用作语法糖。但这需要学习新的语法定义:

/**
 * Syntactic sugar around old callback api returning a promise
 *
 * @returns {promise} Promise of call from server
 * @param {string[]|string} propertyAccesors Array of properties to access
 * @param {object[][]} methodAccesors Array of [method_to_access,arguments[]]
 * @param {number[]} resRejIdxs 2 Indexes of methodAccesors corresponding to resolve/success and rejection/failure. If omitted promise is resolved immediately.
 */
const GS = (propertyAccesors, methodAccesors, resRejIdxs) =>
  new Promise((res, rej) => {
    //Boilerplate for type correction
    const nestArray = e => (Array.isArray(e) ? e : [e]);
    propertyAccesors = nestArray(propertyAccesors);
    methodAccesors = nestArray(methodAccesors);
    methodAccesors[0] = nestArray(methodAccesors[0]);
    if (typeof resRejIdxs !== 'undefined') {
      resRejIdxs = Array.isArray(resRejIdxs) ? resRejIdxs : [resRejIdxs];
      resRejIdxs[0] && (methodAccesors[resRejIdxs[0]][1] = res);
      resRejIdxs[1] && (methodAccesors[resRejIdxs[1]][1] = rej);
    } else {
      res('Done');
    }

    //Access properties and call methods
    methodAccesors.reduce(
      (acc, [method, methodArg]) =>
        Array.isArray(methodArg)
          ? acc[method](...methodArg)
          : acc[method](methodArg),
      propertyAccesors.reduce(
        (acc, currentProp) => acc[currentProp],
        google.script
      )
    );
  });

//EXAMPLES:
GS(
  'run',
  [
    ['withSuccessHandler', null],
    ['callServer', [5, 4]], //call server function `callServer` with 2 arguments 5 and 4
    ['withFailureHandler', null],
  ],
  [0, 2] //0 is withSuccessHandler and 2 is withFailureHandler
).then(alert);

GS('history', [['setChangeHandler', e => console.log(e.location.hash)]]);
GS('url', 'getLocation', 0).then(location => console.log(location.hash));
GS(['host', 'editor'], 'focus');
GS('host', ['setHeight', 50]);

这样的事情还不错:

var doSomeAsyncShit = async () => {
  let location = await new promise(resolve => google.script.url.getLocation(resolve))
  // do stuff with location
}

(async () => {
  await doSomeAsyncShit();
  // do something after
})()

即便如此,您还是增加了复杂性,降低了可读性,并且毫无理由恕我直言