Loop with timeout protection that only runs once “

2020-04-08 12:16发布

问题:

I wrote a script that restores calendar data from a backup written in a spreadsheet. Since the amount of data is highly unpredictable I designed a loop that stops after a given number of minutes , asking the user to continue or to cancel while showing the actual counter state (this to prevent issue with Google max execution time limit).

It works pretty well but in this simplified test script I used for testing the idea it works only once : when the first "timeout" occurs, it shows the continue/cancel option as expected and then continues from where it started but when the same condition happens for the second time the continue button doesn't shows up.

My question is simply : why ? or better : what's the difference between both situations ?

The spreadsheet with embedded script is publicly testable here (see menu : test)

and the whole script is shown below (it's a bit long of course but the interresting part is near the end)

I used ScriptProperties to keep track of execution time and to continue the loop from where I left.

function onOpen() {   
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [
                     {name: "test", functionName: "test"},  
                      ];  
  ss.addMenu("test", menuEntries);  
}

function test(){
  ScriptProperties.setProperty('restorePointers',[0,0].join('@'))
  var app = UiApp.createApplication().setTitle("test");
  app.setHeight(150).setWidth(250);
  var doc = SpreadsheetApp.getActiveSpreadsheet();
  var panel = app.createVerticalPanel();
  var handlerCancel = app.createServerHandler('canceltest');
  var handlerContinue = app.createServerHandler('continuetest');
  var contCHandler = app.createClientHandler();
  var cancel = app.createButton("cancel.", handlerCancel).setId('cancel').setVisible(false);
  var cont = app.createButton('continue',handlerContinue).setId('continue').setVisible(false).addClickHandler(contCHandler);
  var button = app.createButton('start').setId('button');
  var handler = app.createServerClickHandler('runtest');
  handler.addCallbackElement(panel);
  contCHandler.forTargets(button).setEnabled(false).forEventSource().setVisible(false);
  var cHandler = app.createClientHandler().forTargets(cancel).setVisible(true).forEventSource().setVisible(false);
  button.addClickHandler(handler).addClickHandler(cHandler);
  app.add(panel.add(button).add(cont).add(cancel))//.add(trig));
  doc.show(app);
}

function canceltest(e){
  var app = UiApp.getActiveApplication();
   ScriptProperties.setProperty('restoreData','')
   ScriptProperties.setProperty('restorePointers','canceled');
   SpreadsheetApp.getActiveSpreadsheet().toast('  ','restore aborted');
   app.close()
   return app;  
}  

function continuetest(e){
  runtest(e)
  }

function runtest(e){
  var dStart; var dEnd;
  ScriptProperties.setProperty('startrestore',new Date().getTime().toString())
    if(ScriptProperties.getProperty('restoreData')==null||Utilities.jsonStringify(ScriptProperties.getProperties()).indexOf('restoreData')==-1)
      {ScriptProperties.setProperty('restoreData',Utilities.jsonStringify(e))
      }
  var app = UiApp.getActiveApplication();
  var pointers = ScriptProperties.getProperty('restorePointers');
   if(pointers=='0@0'){
   dStart = 0;
   dEnd = 500;
   }else{
   dStart = Number(pointers.split('@')[0]);
   dEnd = Number(pointers.split('@')[1]);
   }
// main loop --------------------------
      for(var ee=dStart;ee<dEnd;++ee){ // main loop
           var ccc = ScriptProperties.getProperty('restorePointers');
           if(ccc=='canceled'){ app.close();return app};

      Utilities.sleep(85); // simulate some activity


   if((ee/10)==parseInt(ee/10)&&ee>0){
     SpreadsheetApp.getActiveSpreadsheet().toast(ee+' steps completed')
       if(new Date().getTime()-Number(ScriptProperties.getProperty('startrestore'))>12000){ ;//  +- 12 sec timeout
           ScriptProperties.setProperty('restorePointers',[ee,dEnd].join('@'));
           app.getElementById('continue').setHTML('continue from '+ee).setVisible(true)
           return app
           }
        }
    }
// end of main loop-----------------  
   ScriptProperties.setProperty('restoreData','')
   ScriptProperties.setProperty('restorePointers',0+'@'+0);
   SpreadsheetApp.getActiveSpreadsheet().toast('normal process end');
   app.close();
   return app;  
} 

回答1:

Here's what's keeping the 'continue' button from updating with each interval. Your Server Handler needs to return an app:

function continuetest(e){
  return runtest(e)  ///<<<
}

This bit, (ee/10)==parseInt(ee/10) is an awkward way to evaluate true every 10th item. Use modulus instead:

if((ee%10==0)&&ee>0){ ///<<< modulus

After each pause, the value of ee is repeated in the toast. This can be fixed by remembering what the last displayed value was, and skipping it.

if (ee == Number(ScriptProperties.getProperty('lastToast'))) continue; ///<<< don't repeat toast
ScriptProperties.setProperty('lastToast',ee);  ///<<<

Full script

function onOpen() {   
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [
                     {name: "test", functionName: "test"},  
                      ];  
  ss.addMenu("test", menuEntries);  
}

function test(){
  ScriptProperties.setProperty('restorePointers',[0,0].join('@'))
  var app = UiApp.createApplication().setTitle("test");
  app.setHeight(150).setWidth(250);
  var doc = SpreadsheetApp.getActiveSpreadsheet();
  var panel = app.createVerticalPanel();
  var handlerCancel = app.createServerHandler('canceltest');
  var handlerContinue = app.createServerHandler('continuetest');
  var contCHandler = app.createClientHandler();
  var cancel = app.createButton("cancel.", handlerCancel).setId('cancel').setVisible(false);
  var cont = app.createButton('continue',handlerContinue).setId('continue').setVisible(false).addClickHandler(contCHandler);
  var start = app.createButton('start').setId('start');
  var handler = app.createServerClickHandler('runtest');
  handler.addCallbackElement(panel);
  contCHandler.forTargets(start).setEnabled(false).forEventSource().setVisible(false);
  var cHandler = app.createClientHandler().forTargets(cancel).setVisible(true).forEventSource().setVisible(false);
  start.addClickHandler(handler).addClickHandler(cHandler);
  app.add(panel.add(start).add(cont).add(cancel))//.add(trig));
  doc.show(app);
}

function canceltest(e){
  var app = UiApp.getActiveApplication();
   ScriptProperties.setProperty('restoreData','')
   ScriptProperties.setProperty('restorePointers','canceled');
   SpreadsheetApp.getActiveSpreadsheet().toast('  ','restore aborted');
   app.close()
   return app;  
}  

function continuetest(e){
  return runtest(e)  ///<<<
  }

function runtest(e){
  var dStart; var dEnd;
  ScriptProperties.setProperty('startrestore',new Date().getTime().toString())
    if(ScriptProperties.getProperty('restoreData')==null||Utilities.jsonStringify(ScriptProperties.getProperties()).indexOf('restoreData')==-1)
      {ScriptProperties.setProperty('restoreData',Utilities.jsonStringify(e))
      }
  var app = UiApp.getActiveApplication();
  var pointers = ScriptProperties.getProperty('restorePointers');
   if(pointers=='0@0'){
   dStart = 0;
   dEnd = 500;
   }else{
   dStart = Number(pointers.split('@')[0]);
   dEnd = Number(pointers.split('@')[1]);
   }
// main loop --------------------------
      for(var ee=dStart;ee<dEnd;++ee){ // main loop
           var ccc = ScriptProperties.getProperty('restorePointers');
           if(ccc=='canceled'){ app.close();return app};

      Utilities.sleep(85); // simulate some activity


   if((ee%10==0)&&ee>0){ ///<<< modulus
     if (ee == Number(ScriptProperties.getProperty('lastToast'))) continue; ///<<< don't repeat toast
     ScriptProperties.setProperty('lastToast',ee);  ///<<<
     SpreadsheetApp.getActiveSpreadsheet().toast(ee+' steps completed')
       if(new Date().getTime()-Number(ScriptProperties.getProperty('startrestore'))>12000) { //  +- 12 sec timeout  
           ScriptProperties.setProperty('restorePointers',[ee,dEnd].join('@'));
           app.getElementById('continue').setHTML('continue from '+ee).setVisible(true)
           return app
           }
        }
    }
// end of main loop-----------------  
   ScriptProperties.setProperty('restoreData','')
   ScriptProperties.setProperty('restorePointers',0+'@'+0);
   SpreadsheetApp.getActiveSpreadsheet().toast('normal process end');
   app.close();
   return app;  
}