如何使库与调用者脚本 PropertiesService 一起使用?

Pau*_*per 5 add-on libraries google-apps-script

在 Google将 import/export API 扩展到容器绑定的 Apps Script 项目之前,我已经将我的大部分项目移到了一个可以使用该 API 的库中,然后将 Google Docs 项目变成了一个只调用库的 shell。

我的问题是让库访问与PropertiesServiceGoogle Doc 项目相同的属性 ( )。由于我有我的 Docs Add-on 的现有用户,我需要继续使用这些属性。

在我的 Google Doc 项目中,我尝试过

$.PropertiesService = PropertiesService;
Run Code Online (Sandbox Code Playgroud)

$我的图书馆在哪里)。

它没有用。该库继续使用自己的属性。

然后我尝试了:

function _mock(obj) {
  var ret = {};
  for(var key in obj) {
    if(typeof obj[key] == 'function') {
      ret[key] = obj[key].bind(obj);
    } else {
      ret[key] = obj[key];
    }
  }
  return ret;
}

$.PropertiesService = _mock(PropertiesService);
Run Code Online (Sandbox Code Playgroud)

还是行不通。再试一次:

function _mock(obj) {
  var ret = {};
  for(var key in obj) {
    if(typeof obj[key] == 'function') {
      ret[key] = (function(val) {
        return function() {
          return val.apply(obj, arguments);
        };
      })(obj[key]);
    } else {
      ret[key] = obj[key];
    }
  }
  return ret;
}

$.PropertiesService = _mock(PropertiesService);
Run Code Online (Sandbox Code Playgroud)

这有效。


此时,我想知道:

  1. 为什么前两种方式行不通,而第三种方式行得通?

  2. 我可以期望它继续工作吗?

  3. 有没有更好的方法让库访问主脚本的属性?

文档很少。有这个,但是PropertiesService没有提到。

Ole*_*ter 4

资源共享

如您所知,图书馆有共享资源和非共享资源。PropertiesService列在非共享资源下,这意味着该库有自己的服务实例,当您在库代码中引用它时可以访问该实例。

const getStore = () => PropertiesService.getScriptProperties();
Run Code Online (Sandbox Code Playgroud)

如果上面的函数在库中声明,它将使用库的资源(如果在调用脚本中) - 它自己的实例。


V8运行时解决方案

V8 运行时不会为您的代码创建特殊上下文,而是让您可以直接访问内置服务。因此,在使用运行时时,可以通过简单地定义或替换全局属性来注入服务this

//in the library;
var getProperty = ((ctxt) => (key) => {
    var service = ctxt.injectedService;
    var store = service.getScriptProperties();
    return store.getProperty(key);
})(this);

var setProperty = ((ctxt) => (key, val) => {
    var service = ctxt.injectedService;
    var store = service.getScriptProperties();
    return store.setProperty(key, val);
})(this);

var inject = ((ctxt) => (service) => ctxt.injectedService = service)(this);

var greet = ((ctxt) => () => {
    var store = ctxt.injectedService.getScriptProperties();
    return store.getProperty("greeting") || "Ola!";
})(this);

//in the calling script;
function testSharedResources() {
  PropertiesService.getScriptProperties().setProperty("greeting", "Hello, lib!");
  $.inject(PropertiesService);
  Logger.log($.greet()); //Hello, lib!
  $.setProperty("greeting", "Hello, world!");
  Logger.log($.greet()); //Hello, world!
}
Run Code Online (Sandbox Code Playgroud)

在某些情况下,全局this将是undefined(我在将库添加到绑定脚本时遇到过这种情况)。在这种情况下,只需定义一个私有全局命名空间(以避免泄漏到调用者脚本):

//in the library;
var Dependencies_ = {
    properties : PropertiesService
};

var use = (service) => {
    if ("getScriptProperties" in service) {
        Dependencies_.properties = service;
    }
};

//in the calling script;
$.use(PropertiesService);
Run Code Online (Sandbox Code Playgroud)

Rhino运行时解决方案

另一方面,较旧的 Rhino 运行时会创建一个特殊的隐式上下文。这意味着您无法访问内置服务或全局this. 您唯一的选择是绕过调用库中的服务(您的方法 #3 非常适合这样做)。


问题

  1. 为什么前两种方法不行,第三种方法却行呢?

您的方法的所有问题都归结为:

  1. 资源共享(图书馆有自己的服务实例)
  2. 特殊的隐式上下文(Rhino 中无法从外部访问内置库)

但有一个问题:所有 3 种方法都按设计工作

首先,如果您特别引用了on ,方法一确实有效。这是有道理的,因为库作为名称空间包含在内,其中的成员映射到库中的全局声明。例如:PropertiesService$

//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
$.PropertiesService = PropertiesService;

Logger.log( $.PropertiesService.getScriptProperties().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"

//in the library
function getProperty(key) {
  var store = PropertiesService.getScriptProperties();
  return store.getProperty(key);
}
Run Code Online (Sandbox Code Playgroud)

方法二也有效。调用者脚本中的函数绑定不会改变这样的事实:如果在库中调用它接收库上下文,但如果直接在调用脚本中调用绑定副本,则它会起作用:

//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
var bound = $.PropertiesService.getScriptProperties.bind(PropertiesService); 

var obj = { getScriptProperties : bound };
  
$.PropertiesService = obj;

Logger.log( bound().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"
Run Code Online (Sandbox Code Playgroud)

现在,为什么第三种方法可以开箱即用?由于封装函数捕获PropertiesService调用脚本并应用该getScriptProperties方法而导致闭包。为了显示:

//in the caller script
var appl = { 
  getScriptProperties : (function(val) { 
    return function() { 
      return val.apply(PropertiesService);
    }; 
  })(PropertiesService.getScriptProperties) 
};
  
$.PropertiesService = appl;
  
Logger.log( $.getProperty("test") ); // "test"
Run Code Online (Sandbox Code Playgroud)
  1. 我可以期望它继续发挥作用吗?

是和不是。是的,因为您的_mock函数行为在所有情况下都表现出预期的行为。不,因为apply依赖于getScriptProperties不被实现为箭头函数,其中this重写将被忽略。

  1. 有没有更好的方法让库访问主脚本的属性?

对于 Rhino 运行时 - 不这么认为。对于 V8 - 直接注入服务就足够了。