客户端 JavaScript 从服务器端文档集和获取函数接收过时的值

Client JavaScript receiving outdated values from server-side document set and fetch functions

本文关键字:函数 过时 获取 JavaScript 服务器端 文档 客户端      更新时间:2023-09-26

我为文档编写了一个Google Apps脚本,用于重复设置段落的文本,然后检索它。如果我在脚本文件中执行此操作,一切正常(请参阅下面的函数"test"):

Code.gs:

function include(filename) {
  return HtmlService.createHtmlOutputFromFile(filename).getContent();
}
function test() {
  DocumentApp.getActiveDocument().getBody().insertParagraph(0, "");
  for(var i = 0; i < 10; i++) {
    Logger.log("Inserting " + i);
    setText(i);
    Logger.log("Received " + getText());
  }
}
function setText(text) {
  var paragraph = DocumentApp.getActiveDocument().getBody().getChild(0).asParagraph();
  paragraph.clear();
  paragraph.setText(text);
}
function getText() {
  var paragraph = DocumentApp.getActiveDocument().getBody().getChild(0).asParagraph();
  return paragraph.getText();
}
function onOpen() {
  test();
  DocumentApp.getUi()
      .createMenu('Test')
      .addItem('Open', 'openTest')
      .addToUi();
}
function openTest() {
  var html = HtmlService
      .createTemplateFromFile('index')
      .evaluate()
      .setSandboxMode(HtmlService.SandboxMode.IFRAME);
  DocumentApp.getUi().showSidebar(html);
}

即测试函数的行为符合预期:

[16-03-02 06:06:58:652 PST] Inserting 0
[16-03-02 06:06:58:657 PST] Received 0
[16-03-02 06:06:58:658 PST] Inserting 1
[16-03-02 06:06:58:663 PST] Received 1
[16-03-02 06:06:58:664 PST] Inserting 2
[16-03-02 06:06:58:670 PST] Received 2
[16-03-02 06:06:58:670 PST] Inserting 3
[16-03-02 06:06:58:676 PST] Received 3
[16-03-02 06:06:58:677 PST] Inserting 4
[16-03-02 06:06:58:683 PST] Received 4
[16-03-02 06:06:58:684 PST] Inserting 5
[16-03-02 06:06:58:690 PST] Received 5
[16-03-02 06:06:58:690 PST] Inserting 6
[16-03-02 06:06:58:696 PST] Received 6
[16-03-02 06:06:58:697 PST] Inserting 7
[16-03-02 06:06:58:703 PST] Received 7
[16-03-02 06:06:58:703 PST] Inserting 8
[16-03-02 06:06:58:708 PST] Received 8
[16-03-02 06:06:58:709 PST] Inserting 9
[16-03-02 06:06:58:715 PST] Received 9

但是,如果我做同样的事情但从 HTML 页面:

JavaScript:

<script>
var i = 0;
$(document).ready(insert);
function log(msg) {
   $('#log').append('<div></div>').append(document.createTextNode(msg));
}
function insert() {
    log("Inserting " + i);
    google.script.run.withSuccessHandler(receive).setText(i);
}
function receive() {
    google.script.run.withSuccessHandler(function(text) {
        log("Received " + text);
        i++;
        if(i < 10)
            insert();
    }).getText();
}
</script>

我得到的结果是这样的

Inserting 0
Received
Inserting 1
Received 1
Inserting 2
Received 2
Inserting 3
Received 3
Inserting 4
Received 3
Inserting 5
Received 5
Inserting 6
Received 6
Inserting 7
Received 7
Inserting 8
Received 8
Inserting 9
Received 9 
有些是

有序的,但有些是交换的。为什么会这样?当我的 withSuccess 回调被调用时,我对文档的更改不应该生效吗?是否有可以将回调传递给的其他函数?

您看到的是异步执行引起的竞争条件的结果,因为JavaScript代码与Google Apps Script测试函数执行的操作不同,尽管您努力确保每个setText()getText()函数调用都是同步的。(对于不熟悉这些概念的人来说,这里有一个很好的一般解释:异步与同步执行,它的真正含义是什么?

问题是,这里涉及另一个级别的异步性,因为您正在访问的Google文档的每个"用户"都会获得自己的副本,该副本(至少在概念上)与主副本同步。

test()函数在单个 Google Apps 脚本执行实例的上下文中运行。该实例与文档的一个副本相关联,并且所有后续读取和写入都与该副本相关联。实际效果是,您的脚本在进行更改后立即看到它们。

将此与在客户端浏览器中运行的 JavaScript 的行为进行对比。在那里,每个google.script.run调用都会在Google的服务器上创建一个新的,唯一的Google Apps Script执行实例,并具有自己唯一的Google文档副本。调用setText()时,它会修改其文档副本,然后与主控文档同步。当getText()运行时,它会获取当前主控文档的副本,并从中检索内容。如果上一个setText()更改的同步已完成,您将获得所需的输出。如果同步没有完成 - 好吧,你会得到你得到的。

你能做些什么呢?只需最少的代码更改,您就可以延迟来自 JavaScript 客户端的getText()调用,只要您认为有必要保证成功。需要注意的是,今天足够的延迟可能不是明天,因此不可靠。

function receive() {
    setTimeout(
        google.script.run.withSuccessHandler(function(text) {
            log("Received " + text);
            i++;
            if(i < 10)
                insert();
        }).getText(),
        2000         // 2 second delay before calling getText()
    );
}

为了保证同步性,您需要设计一个更复杂的系统,例如一些始终运行的"服务器"进程,并需要操作文档的单个副本。