Asynchronous 函数应用程序脚本的异步执行

Asynchronous 函数应用程序脚本的异步执行,asynchronous,google-apps-script,promise,callback,Asynchronous,Google Apps Script,Promise,Callback,我一直在四处挖掘,但我找不到关于如何在谷歌应用程序脚本中使用异步函数的参考或文档,我发现人们提到这是可能的,但没有提到如何 有人能给我指出正确的方向或给我举个例子吗? 承诺,回电,或者其他可以帮助我的事情 我有一个函数,让我们调用它foo,它需要一段时间才能执行(足够长,可以超时HTTP调用) 我想做的是重构它,使其工作方式如下: function doPost(e) { // parsing and getting values from e var returnable =

我一直在四处挖掘,但我找不到关于如何在谷歌应用程序脚本中使用异步函数的参考或文档,我发现人们提到这是可能的,但没有提到如何

有人能给我指出正确的方向或给我举个例子吗? 承诺,回电,或者其他可以帮助我的事情

我有一个函数,让我们调用它
foo
,它需要一段时间才能执行(足够长,可以超时HTTP调用)

我想做的是重构它,使其工作方式如下:

function doPost(e) {
    // parsing and getting values from e
    var returnable = foo(par1, par2, par3);
      return ContentService
             .createTextOutput(JSON.stringify(returnable))
             .setMimeType(ContentService.MimeType.JSON);
}

function foo(par1, par2, par3) {
    var returnable = something(par1, par2, par3); // get the value I need to return;

    // continue in an Async way, or schedule execution for something else
    // and allow the function to continue its flow
    /* async bar(); */

    return returnable;
}
现在我想了解
foo
中的这一点,因为它需要很长时间,我不想冒超时的风险,而且那里发生的逻辑是完全独立于客户端的,所以没关系,我只需要返回值,我将在之前得到它

另外,我认为值得一提的是,它作为一个web应用程序部署在Google Drive中

很久没有这样做了,但添加一些上下文,当时我想在Google Drive上安排几件事情,它正在计时执行,所以我正在寻找一种安全地安排作业的方法

  • 您希望使用Google Apps脚本通过异步处理执行函数
  • 您希望使用时间触发器通过异步处理运行函数
如果我的理解是正确的,不幸的是,没有直接实现这一目标的方法和正式文件。但作为一种解决办法,这可以通过使用GoogleAppsScriptAPI和fetchAll方法来实现,后者可以通过异步处理工作

此解决方案的流程如下所示

  • 部署API可执行文件,启用Google Apps脚本API
  • 使用fetchAll,请求googleapps脚本API的端点以运行函数。
    • 当一次请求多个函数时,这些函数通过fetchAll进行异步处理
  • 注:
    • 我认为Web应用程序也可以用来代替Google应用程序脚本API
    • 为了简单地使用这个变通方法,我创建了一个。我想你也可以用它
    • 在此解决方案中,还可以使用时间触发器运行异步处理的函数
    参考资料:

    如果我误解了你的问题,很抱歉。

    还有另一种方法可以做到这一点

    您可以使用异步运行函数,它们需要一点时间排队(30-60秒),但对于要从脚本的主执行中删除的运行缓慢的任务来说,它非常理想

    // Creates a trigger that will run a second later
    ScriptApp.newTrigger("myFunction")
      .timeBased()
      .after(1)
      .create();
    
    我编写了一个名为
    Async.gs
    的方便脚本,它将有助于从该技术中删除样板文件。您甚至可以使用它通过传递参数

    以下是链接:

    //定义异步函数
    函数runSlowTask(用户标识,处于活动状态){
    log('runSlowTask()',{user\u id:user\u id,is\u active:is\u active});
    公用事业.睡眠(5000);
    log('runSlowTask()-FINISHED!')
    }
    //异步运行函数
    调用('runSlowTask');
    //使用一个参数异步运行函数
    Async.call('runSlowTask',51291);
    //使用多个参数异步运行函数
    调用('runSlowTask',51291,true);
    //使用参数数组异步运行函数
    apply('runSlowTask',[51291,true]);
    //使用一个参数异步运行库中的函数
    调用('MyLibrary.runSlowTask',51291);
    //使用参数数组异步运行库中的函数
    apply('MyLibrary.runSlowTask',[51291,true]);
    
    基于此,我创建了它的另一个版本。我的目标是:

    • 易于维护
    • 易于调用(简单调用约定)
    任务.gs

    class TasksNamespace {
      constructor() {
        this.webAppDevUrl = 'https://script.google.com/macros/s/<your web app's dev id>/dev';
        this.accessToken = ScriptApp.getOAuthToken();
      }
    
      // send all requests
      all(requests) {
        return requests
        .map(r => ({
          muteHttpExceptions: true,
          url: this.webAppDevUrl,
          method: 'POST',
          contentType: 'application/json',
          payload: {
            functionName: r.first(),
            arguments: r.removeFirst()
          }.toJson(),
          headers: {
            Authorization: 'Bearer ' + this.accessToken
          }
        }), this)
        .fetchAll()
        .map(r => r.getContentText().toObject())
      }
    
      // send all responses
      process(request) {
        return ContentService
        .createTextOutput(
          request
          .postData
          .contents
          .toObject()
          .using(This => ({
            ...This,
            result: (() => {
              try {
                return eval(This.functionName).apply(eval(This.functionName.splitOffLast()), This.arguments) // this could cause an error
              }
              catch(error) {
                return error;
              }
            })()
          }))
          .toJson()
        )
        .setMimeType(ContentService.MimeType.JSON)
      }
    }
    
      // array prototype
    
      Array.prototype.fetchAll = function() {
        return UrlFetchApp.fetchAll(this);
      }
    
      Array.prototype.first = function() {
        return this[0];
      }
    
      Array.prototype.removeFirst = function() {
        this.shift();
        return this;
      }
    
      Array.prototype.removeLast = function() {
        this.pop();
        return this;
      }
    
    
      // string prototype
    
      String.prototype.blankToUndefined = function(search) {
        return this.isBlank() ? undefined : this;
      };    
    
      String.prototype.isBlank = function() {
        return this.trim().length == 0;
      }
    
      String.prototype.splitOffLast = function(delimiter = '.') {
        return this.split(delimiter).removeLast().join(delimiter).blankToUndefined();
      }
    
      // To Object - if string is Json
      String.prototype.toObject = function() {
        if(this.isBlank())
          return {};
        return JSON.parse(this, App.Strings.parseDate);
      }
    
      // object prototype
    
      Object.prototype.toJson = function() {
        return JSON.stringify(this);
      }
    
      Object.prototype.using = function(func) {
        return func.call(this, this);
      }
    
    function doPost(request) {
      return new TasksNamespace.process(request);
    }
    
    http.handler.gs

    class TasksNamespace {
      constructor() {
        this.webAppDevUrl = 'https://script.google.com/macros/s/<your web app's dev id>/dev';
        this.accessToken = ScriptApp.getOAuthToken();
      }
    
      // send all requests
      all(requests) {
        return requests
        .map(r => ({
          muteHttpExceptions: true,
          url: this.webAppDevUrl,
          method: 'POST',
          contentType: 'application/json',
          payload: {
            functionName: r.first(),
            arguments: r.removeFirst()
          }.toJson(),
          headers: {
            Authorization: 'Bearer ' + this.accessToken
          }
        }), this)
        .fetchAll()
        .map(r => r.getContentText().toObject())
      }
    
      // send all responses
      process(request) {
        return ContentService
        .createTextOutput(
          request
          .postData
          .contents
          .toObject()
          .using(This => ({
            ...This,
            result: (() => {
              try {
                return eval(This.functionName).apply(eval(This.functionName.splitOffLast()), This.arguments) // this could cause an error
              }
              catch(error) {
                return error;
              }
            })()
          }))
          .toJson()
        )
        .setMimeType(ContentService.MimeType.JSON)
      }
    }
    
      // array prototype
    
      Array.prototype.fetchAll = function() {
        return UrlFetchApp.fetchAll(this);
      }
    
      Array.prototype.first = function() {
        return this[0];
      }
    
      Array.prototype.removeFirst = function() {
        this.shift();
        return this;
      }
    
      Array.prototype.removeLast = function() {
        this.pop();
        return this;
      }
    
    
      // string prototype
    
      String.prototype.blankToUndefined = function(search) {
        return this.isBlank() ? undefined : this;
      };    
    
      String.prototype.isBlank = function() {
        return this.trim().length == 0;
      }
    
      String.prototype.splitOffLast = function(delimiter = '.') {
        return this.split(delimiter).removeLast().join(delimiter).blankToUndefined();
      }
    
      // To Object - if string is Json
      String.prototype.toObject = function() {
        if(this.isBlank())
          return {};
        return JSON.parse(this, App.Strings.parseDate);
      }
    
      // object prototype
    
      Object.prototype.toJson = function() {
        return JSON.stringify(this);
      }
    
      Object.prototype.using = function(func) {
        return func.call(this, this);
      }
    
    function doPost(request) {
      return new TasksNamespace.process(request);
    }
    
    呼叫约定

    只需使用完整的函数名创建数组,其余的都是函数的参数。当一切都完成时,它将返回,因此它就像
    Promise.all()

    返回预览

    [ { functionName: 'App.Data.Firebase.Properties.getById',
        arguments: [ 'T006DB4' ],
        result: 
         { Id: '',
           Listings: [Object],
           Pages: [Object],
           TempId: 'T006DB4',
           Workflow: [Object] } },
    ...
    ]
    
    注释

    class TasksNamespace {
      constructor() {
        this.webAppDevUrl = 'https://script.google.com/macros/s/<your web app's dev id>/dev';
        this.accessToken = ScriptApp.getOAuthToken();
      }
    
      // send all requests
      all(requests) {
        return requests
        .map(r => ({
          muteHttpExceptions: true,
          url: this.webAppDevUrl,
          method: 'POST',
          contentType: 'application/json',
          payload: {
            functionName: r.first(),
            arguments: r.removeFirst()
          }.toJson(),
          headers: {
            Authorization: 'Bearer ' + this.accessToken
          }
        }), this)
        .fetchAll()
        .map(r => r.getContentText().toObject())
      }
    
      // send all responses
      process(request) {
        return ContentService
        .createTextOutput(
          request
          .postData
          .contents
          .toObject()
          .using(This => ({
            ...This,
            result: (() => {
              try {
                return eval(This.functionName).apply(eval(This.functionName.splitOffLast()), This.arguments) // this could cause an error
              }
              catch(error) {
                return error;
              }
            })()
          }))
          .toJson()
        )
        .setMimeType(ContentService.MimeType.JSON)
      }
    }
    
      // array prototype
    
      Array.prototype.fetchAll = function() {
        return UrlFetchApp.fetchAll(this);
      }
    
      Array.prototype.first = function() {
        return this[0];
      }
    
      Array.prototype.removeFirst = function() {
        this.shift();
        return this;
      }
    
      Array.prototype.removeLast = function() {
        this.pop();
        return this;
      }
    
    
      // string prototype
    
      String.prototype.blankToUndefined = function(search) {
        return this.isBlank() ? undefined : this;
      };    
    
      String.prototype.isBlank = function() {
        return this.trim().length == 0;
      }
    
      String.prototype.splitOffLast = function(delimiter = '.') {
        return this.split(delimiter).removeLast().join(delimiter).blankToUndefined();
      }
    
      // To Object - if string is Json
      String.prototype.toObject = function() {
        if(this.isBlank())
          return {};
        return JSON.parse(this, App.Strings.parseDate);
      }
    
      // object prototype
    
      Object.prototype.toJson = function() {
        return JSON.stringify(this);
      }
    
      Object.prototype.using = function(func) {
        return func.call(this, this);
      }
    
    function doPost(request) {
      return new TasksNamespace.process(request);
    }
    
    • 它可以处理任何静态方法、根对象树上的任何方法或任何根(全局)函数
    • 它可以处理0个或更多(任意数量)的任何类型的参数
    • 它通过从任何post返回错误来处理错误
    有了新功能,现在可以在应用程序脚本中编写和使用承诺

    甚至可以声明为异步!例如(typescript):

    要开始使用新的运行时,只需遵循以下步骤。简言之,这一切归结为在
    appsscript.json
    文件中添加以下行:

    {
      ...
      "runtimeVersion": "V8"
    }
    

    如果从客户端HTML发出多个
    google.script.run.myFunction()
    调用,则被调用的函数和在服务器上运行的函数可以同时运行。这是你想要的情况,还是你想要一些不同的东西?例如,如果首次在浏览器中加载某些HTML,并且您希望在初始加载后注入更多内容,您可以进行多个
    google.script.run.myFunction()
    调用,并在服务器上运行多个代码实例,当每个实例完成时,它会处理返回。问题是,我是否可以编辑,我在HTTP调用期间使用它,客户端调用应用程序脚本
    doPost
    ,它需要计划执行,同时返回在计划执行之前获得的特定值。如果我已经将脚本部署为WebApp,这仍然适用?@ekiim Yes。您还可以使用Web应用程序使用此解决方案。如果要使用Web应用,请使用fetchApp请求Web应用的终结点。但请注意,这是工人的限制。虽然我认为这与API可执行文件是一样的,但请在您的en下对此进行确认