【问题标题】:Google Scripts spreadsheet script: function within for loop does not fully execute before loop finishesGoogle Scripts 电子表格脚本:for 循环中的函数在循环完成前未完全执行
【发布时间】:2014-05-06 01:14:54
【问题描述】:

我正在编写一个用于 google 电子表格的脚本。我希望脚本循环浏览 URL 电子表格。在循环过程中,该函数应该调用一个 UI 实例,该实例显示 url 链接并接收有关 url 为“当前”或“过期”的用户输入,并将其标记为电子表格。

理想情况下,在用户选择一个选项并单击“下一步”按钮后,该 url 的对话框将关闭,而下一个 url 的对话框将打开。

代码如下。

   function getUrlsToCheck(){

   /**
   * Asks user to input range of urls to check.
   */

   var spreadsheet = SpreadsheetApp.getActive();
   var sheet = spreadsheet.getSheets()[0];

  // Prompt the user for the range with the starting row and ending row.
   var rowStart = Browser.inputBox('Starting Entry',
      'Please enter the starting row of the urls you want to check' +
      ' (for example, "1"):',
      Browser.Buttons.OK_CANCEL);
    if (rowStart == 'cancel') {
    return;
  }

    var rowNumber1 = Number(rowStart);
       if (isNaN(rowNumber1) || rowNumber1 < 2 ||
        rowNumber1 > spreadsheet.getLastRow()) {
        Browser.msgBox('Error',
        Utilities.formatString('Row "%s" is not a valid starting point.', rowStart),
        Browser.Buttons.OK);
    return; 
  }

   var rowEnd = Browser.inputBox('Ending Entry',
      'Please enter the ending row of the urls you want to check' +
       ' (for example, "56"):',
       Browser.Buttons.OK_CANCEL);
    if (rowEnd == 'cancel') {
    return;
  }

    var rowNumber2 = Number(rowEnd);
    if (isNaN(rowNumber2) || rowNumber2 < rowNumber1 || rowNumber1 == rowNumber2) {
        Browser.msgBox('Error',
        Utilities.formatString('Row "%s" is not a valid ending point.', rowEnd),
        Browser.Buttons.OK);
    return; 
}

    var numOfRowsToCheck = (rowNumber2 - rowNumber1);
    var range = sheet.getRange(rowNumber1, 1, (numOfRowsToCheck + 1), 1);
    var data = range.getValues();

  /*
   *Loop to retrieve URLs and show a link in the Ui instance
   */

    for(var i = 0; i < data.length; i++) {

    var activeRow = (Number(i) + rowNumber1);

    var range4 = sheet.getRange(activeRow, 4);
    var range5 = sheet.getRange(activeRow, 5);

    var app = UiApp.createApplication().setHeight(150).setWidth(250);
    app.setTitle("Check URL");

    var link1 = app.createAnchor('Check', data[i]);
    var panel = app.createVerticalPanel();

    var infoLabel = app.createLabel(Utilities.formatString('Check the link for entry %s', activeRow)).setId('infoLabel');
    var infoLabel1 = app.createLabel('').setId('infoLabel1');

    var current = app.createRadioButton("group", "Current").setName('current').setId('current');
    var expired = app.createRadioButton("group", "Expired").setName('expired').setId('expired');

    var hidden = app.createHidden("yourObject", Utilities.jsonStringify(activeRow));

    var handler = app.createServerChangeHandler('current');
    handler.addCallbackElement(panel).addCallbackElement(hidden);
    current.addClickHandler(handler);

    var handler2 = app.createServerChangeHandler('expired');
    handler2.addCallbackElement(panel).addCallbackElement(hidden); 
    expired.addClickHandler(handler2);

    var next = app.createButton('Next').setId('next');
    var handler3 = app.createServerHandler('next');
    handler3.addCallbackElement(panel);
    next.addClickHandler(handler3);

    panel.setSpacing(5);

    panel.add(infoLabel);
    panel.add(link1);
    panel.add(current);      
    panel.add(expired);
    panel.add(infoLabel1);
    panel.add(next);

    app.add(panel);

    spreadsheet.show(app);  
   }     
  }

     function next(e){
     var app = UiApp.getActiveApplication(); 
     app.close();

   return app;
  }

function current(e){
  var app = UiApp.getActiveApplication(); 
  app.getElementById('expired').setValue(false);

  var yourObj = Utilities.jsonParse(e.parameter.yourObject);

  var spreadsheet = SpreadsheetApp.getActive();
  var sheet = spreadsheet.getSheets()[0];
  var range4 = sheet.getRange(yourObj, 4);
  var range5 = sheet.getRange(yourObj, 5);

  range4.setValue('x');
  range5.setValue('');

  app.getElementById('infoLabel1').setText('This link has been marked current');

  return app;
 }

function expired(e){
  var app = UiApp.getActiveApplication(); 
  app.getElementById('current').setValue(false);

  var yourObj = Utilities.jsonParse(e.parameter.yourObject);

  var spreadsheet = SpreadsheetApp.getActive();
  var sheet = spreadsheet.getSheets()[0];
  var range4 = sheet.getRange(yourObj, 4);
  var range5 = sheet.getRange(yourObj, 5);

  range4.setValue('');
  range5.setValue('x');

 app.getElementById('infoLabel1').setText('This link has been marked expired'); 

  return app;
 }

我遇到的问题是循环循环时,没有足够的时间让接受用户信息的对话框加载;所以你只会看到最后一个 url 的对话框。

我尝试了几件事,但我一定做错了,因为它们没有奏效。

  • 我已尝试插入系统暂停。

  • 我也尝试过将该函数设为生成器函数,但
    yield 命令编译不正确。

  • 我也尝试过使用 do while 循环而不是 for 循环。
    虽然条件与下一个按钮是否是相关的
    已禁用。

关于如何让函数的每一步在循环结束之前执行有什么建议吗?

【问题讨论】:

  • 我对谷歌电子表格的经验很少,但这里有一个可能可行的建议:您能否将脚本分解为 2 个或更多脚本,并在用户单击链接为当前或已过期时运行脚本?

标签: javascript for-loop google-apps-script google-sheets do-while


【解决方案1】:

此代码的问题在于,它同时创建了所有对话框,但您只想在完成第一个对话框后才创建对话框。重写很多东西可能会更好,但是对于这个演示,我只是将您的代码转换为能够同步运行。

因此,您必须在回调处理程序next 中创建对话框,而不是在getUrlsToCheck 中的for 循环中。这确实意味着我们必须基本上制作一个recursive call。我们还必须跟踪i 并将其传递。为了尽量减少对代码的编辑,我将getUrlsToCheck 中的大部分代码移至一个函数,该函数从参数irowNumber1rowNumber2 初始化所需数据并打开对话框。

我还在verify 中添加了一些代码,将数据i+1rowNumber1rowNumber 传递给next 处理程序。在next 中,我们调用verify 并使用传递的处理程序的解析JSON。

最后开始循环,我们通过传递rowNumber1rowNumber2i=0getUrlsToCheck 中调用verify

所以代码看起来像

function getUrlsToCheck() {
    /**
     * Asks user to input range of urls to check.
     */

    var spreadsheet = SpreadsheetApp.getActive();
    var sheet = spreadsheet.getSheets()[0];

    // Prompt the user for the range with the starting row and ending row.
    var rowStart = Browser.inputBox('Starting Entry',
        'Please enter the starting row of the urls you want to check' +
        ' (for example, "1"):',
        Browser.Buttons.OK_CANCEL);
    if (rowStart == 'cancel') {
        return;
    }

    var rowNumber1 = Number(rowStart);
    if (isNaN(rowNumber1) || rowNumber1 < 2 ||
        rowNumber1 > spreadsheet.getLastRow()) {
        Browser.msgBox('Error',
            Utilities.formatString('Row "%s" is not a valid starting point.', rowStart),
            Browser.Buttons.OK);
        return;
    }

    var rowEnd = Browser.inputBox('Ending Entry',
        'Please enter the ending row of the urls you want to check' +
        ' (for example, "56"):',
        Browser.Buttons.OK_CANCEL);
    if (rowEnd == 'cancel') {
        return;
    }

    var rowNumber2 = Number(rowEnd);
    if (isNaN(rowNumber2) || rowNumber2 < rowNumber1 || rowNumber1 == rowNumber2) {
        Browser.msgBox('Error',
            Utilities.formatString('Row "%s" is not a valid ending point.', rowEnd),
            Browser.Buttons.OK);
        return;
    }

    var numOfRowsToCheck = (rowNumber2 - rowNumber1);


    /*
     *Moved everything to function verify();
     */
    verify({
        i: 0,
        rowNumber1: rowNumber1,
        rowNumber2: rowNumber2
    })
}

function verify(myObj) {
    //gets variable from passed json obj
    var i = myObj.i 
    var rowNumber1 = myObj.rowNumber1
    var rowNumber2 = myObj.rowNumber2

    //mostly the same
    var activeRow = (Number(i) + rowNumber1);

    var spreadsheet = SpreadsheetApp.getActive();
    var sheet = spreadsheet.getSheets()[0];
    var numOfRowsToCheck = (rowNumber2 - rowNumber1);
    var range = sheet.getRange(rowNumber1, 1, (numOfRowsToCheck + 1), 1);
    var data = range.getValues();
    var range4 = sheet.getRange(activeRow, 4);
    var range5 = sheet.getRange(activeRow, 5);

    if (i < data.length) {

        var app = UiApp.createApplication().setHeight(150).setWidth(250);
        app.setTitle("Check URL");

        var link1 = app.createAnchor('Check', data[i]);
        var panel = app.createVerticalPanel();

        var infoLabel = app.createLabel(Utilities.formatString('Check the link for entry %s', activeRow)).setId('infoLabel');
        var infoLabel1 = app.createLabel('').setId('infoLabel1');

        var current = app.createRadioButton("group", "Current").setName('current').setId('current');
        var expired = app.createRadioButton("group", "Expired").setName('expired').setId('expired');

        var hidden = app.createHidden("yourObject", Utilities.jsonStringify(activeRow));

        var handler = app.createServerChangeHandler('current');
        handler.addCallbackElement(panel).addCallbackElement(hidden);
        current.addClickHandler(handler);

        var handler2 = app.createServerChangeHandler('expired');
        handler2.addCallbackElement(panel).addCallbackElement(hidden);
        expired.addClickHandler(handler2);

        //added this to pass state to next()
        var hidden = app.createHidden("yourObject", Utilities.jsonStringify({
            i: i + 1, //increment i
            rowNumber1: rowNumber1,
            rowNumber2: rowNumber2
        }));

        var next = app.createButton('Next').setId('next');
        var handler3 = app.createServerHandler('next');
        handler3.addCallbackElement(panel);
        handler3.addCallbackElement(panel).addCallbackElement(hidden);
        next.addClickHandler(handler3);

        panel.setSpacing(5);

        panel.add(infoLabel);
        panel.add(link1);
        panel.add(current);
        panel.add(expired);
        panel.add(infoLabel1);
        panel.add(next);

        app.add(panel);

        spreadsheet.show(app);
    }
}


function next(e) {
    var app = UiApp.getActiveApplication();
    app.close();

    //parse string to json and call verify to open next dialog
    verify(Utilities.jsonParse(e.parameter.yourObject));

    return app;
}

function current(e) {
    var app = UiApp.getActiveApplication();
    app.getElementById('expired').setValue(false);

    var yourObj = Utilities.jsonParse(e.parameter.yourObject);

    var spreadsheet = SpreadsheetApp.getActive();
    var sheet = spreadsheet.getSheets()[0];
    var range4 = sheet.getRange(yourObj, 4);
    var range5 = sheet.getRange(yourObj, 5);

    range4.setValue('x');
    range5.setValue('');

    app.getElementById('infoLabel1').setText('This link has been marked current');

    return app;
}

function expired(e) {
    var app = UiApp.getActiveApplication();
    app.getElementById('current').setValue(false);

    var yourObj = Utilities.jsonParse(e.parameter.yourObject);

    var spreadsheet = SpreadsheetApp.getActive();
    var sheet = spreadsheet.getSheets()[0];
    var range4 = sheet.getRange(yourObj, 4);
    var range5 = sheet.getRange(yourObj, 5);

    range4.setValue('');
    range5.setValue('x');

    app.getElementById('infoLabel1').setText('This link has been marked expired');

    return app;
}

我们可能会做得更好,但这在测试中有效。

【讨论】:

  • 感谢您这么长的回答。本质是:您必须将所有内容存储在隐藏字段中,例如无状态 Web 应用程序中。这在电子表格 GUI 中感觉很奇怪,但这是有道理的,因为它只是一个将 http-requests 发送到非常大范围的服务器的网页。您能否在答案中添加一个这样的短标题?
  • @flaschenpost 我认为在这种情况下,对话框是异步的。当我们使用createServerHandler() 将命令发送到谷歌服务器时,我们必须等待它完成做某事。然后循环立即发送所有内容,这就是为什么只有最后一个在工作的原因。所以我们必须制作对话框,在我们完成之前的对话框之后。你是对的,我们必须通过服务器调用传递带有隐藏字段的信息。
猜你喜欢
  • 1970-01-01
  • 2015-02-28
  • 2015-03-04
  • 1970-01-01
  • 1970-01-01
  • 2017-08-27
  • 2012-06-26
  • 1970-01-01
  • 2021-02-15
相关资源
最近更新 更多