如何使库与调用者脚本PropertiesService一起使用? [英] How to make the library work with the caller script PropertiesService?

查看:225
本文介绍了如何使库与调用者脚本PropertiesService一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

直到Google 扩展导入/导出针对容器绑定的Apps脚本项目的API ,我已经将大部分项目移到了可以使用该API的库中,然后将Google Docs项目制作到了可以直接调用该库的外壳中.

我的问题是让库访问相同的属性( PropertiesService )作为Google Doc项目.由于我的文档加载项已有用户,因此我需要继续使用这些属性.

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

$.PropertiesService = PropertiesService;

(其中$是我的库).

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

所以我尝试了:

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

仍然无法正常工作.再试一次:

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

这有效.


在这一点上,我想知道:

  1. 为什么前两种方法不起作用,而第三种方法却起作用?

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

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

文档稀疏.有,但未提及PropertiesService.

解决方案

共享资源

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

const getStore = () => PropertiesService.getScriptProperties();

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


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!
}

在某些情况下,全局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);


Rhino运行时解决方案

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


问题

  1. 为什么前两种方法不起作用,而第三种方法却起作用?

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

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

但是有一个陷阱:所有3种方法都能按设计工作.

首先,如果您专门引用$上的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);
}

处理两个也有效.如果在库中调用 ,调用者脚本中的函数绑定不会改变事实,它会接收到库上下文,但是如果直接在调用脚本中调用绑定副本,它将起作用:

//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"

现在,为什么第三种方法开箱即用?由于包装函数捕获调用脚本 并应用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"

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

是,不是.是的,因为您的_mock函数行为在所有情况下都表现出预期的行为.否,因为apply依赖于getScriptProperties而不是被实现为箭头功能,其中this覆盖将被忽略.

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

对于Rhino运行时-不要这样.对于V8-直接注入服务就足够了.

Until Google extends the import/export API to container-bound Apps Script projects, I have moved most of my project to a library which can use that API, and then made Google Docs project into a shell that just calls through to the library.

My problem is having the library access the same properties (PropertiesService) as the Google Doc project. Since I have existing users of my Docs Add-on, I need to keep using these properties.

In my Google Doc project, I tried

$.PropertiesService = PropertiesService;

(where $ is my library).

It didn't work. The library kept using its own properties.

So then I tried:

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

Still not working. Trying again:

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

This works.


At this point, I'm wondering:

  1. Why did the first two ways not work, but the third way did?

  2. Can I expect this to continue working?

  3. Is there a better way to have a library access the main script's properties?

Documentation is sparse. There is this, but the PropertiesService is not mentioned.

解决方案

Sharing of resources

As you are aware, libraries have shared and non-shared resources. PropertiesService is listed under non-shared resources, meaning that the library has its own instance of the service that is accessed when you reference it in the library code.

const getStore = () => PropertiesService.getScriptProperties();

If the function above is declared in the library, it will use the library's resource, if in the calling script - its own instance.


V8 runtime solution

V8 runtime does not create a special context for your code and gives you access to built-in services directly. Because of this when using the runtime, the service can be injected by simply defining or replacing a property on a global 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!
}

In some contexts global this will be undefined (I encountered this when adding a library to a bound script). In this case, simply define a private global namespace (to avoid leaking to the caller script):

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

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

//in the calling script;
$.use(PropertiesService);


Rhino runtime solution

Older Rhino runtime, on the other hand, creates a special implicit context. This means that you have no access to built-in services or the global this. Your only option is to bypass calling the service in the library (your approach #3 is perfect for doing so).


Questions

  1. Why did the first two ways not work, but the third way did?

All issues with your approaches boil down to:

  1. Resource sharing (libraries have their own service instances)
  2. Special implicit context (no external access to lib built-ins in Rhino)

But there is a catch: all 3 approaches do work as designed.

First, Approach one does work if you specifically reference the PropertiesService on $. This makes sense as the library is included as a namespace with members mapped to global declarations in the library. For example:

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

Approach two also works. Binding of the function in the caller script does not change the fact if called in the library it receives library context, but if you call the bound copy directly in the calling script, it works:

//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"

Now, why does the third approach work out of the box? Because of the closure resulting from the wrapped function capturing the PropertiesService of the calling script and applying the getScriptProperties method. To illustrate:

//in the caller script
var appl = { 
  getScriptProperties : (function(val) { 
    return function() { 
      return val.apply(PropertiesService);
    }; 
  })(PropertiesService.getScriptProperties) 
};
  
$.PropertiesService = appl;
  
Logger.log( $.getProperty("test") ); // "test"

  1. Can I expect this to continue working?

Yes and no. Yes, because your _mock function behavior exhibits the expected behavior in all cases. No, because apply relies on the getScriptProperties not being implemented as an arrow function where this override will be ignored.

  1. Is there a better way to have library access the main script's properties?

For Rhino runtime - don't think so. For V8 - direct injection of the service will suffice.

这篇关于如何使库与调用者脚本PropertiesService一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆