24

Where can I read documentation concerning the execution order rules for GS files?

To dimension the problem I created two trivial objects, each in their own file.

1_File.gs

var ObjB = new Object();
ObjB.sayName = "[" + ObjA.sayName + "]";

0_File.gs

var ObjA = new Object();
ObjA.sayName = " I'm A ";

A call such as ...

Logger.log(ObjA.sayName + " : " + ObjB.sayName);

... gets the error ...

TypeError: Cannot read property "sayName" from undefined.

If I move the code from 1_File.gs into 0_File.gs, and vice versa, then there is no error and the log shows correctly ...

I'm A : [ I'm A ]

Renaming 0_File.gs to 2_File.gs doesn't affect execution order either, so I assume that order depends on which file gets created first.

Is there no concept of "include" or "import" that would allow me to make order of execution explicit?

Martin Bramwell
  • 2,003
  • 2
  • 19
  • 35
  • Whether the order of `gs` files matters appears to have changed (possibly more than once). As of June 2022 release notes: "Now, the order of files in the Apps Script editor doesn't matter. " https://developers.google.com/apps-script/releases#june_2022 – pestophagous Jul 05 '23 at 14:01

7 Answers7

13

Where can I read documentation concerning the execution order rules for GS files?

There is no such documentation and I think will not be any time published. In similar way, an initialization order of the static variables in C++ is also undefined and depends on compiler/linker.

Is there no concept of "include" or "import" that would allow me to make order of execution explicit?

Yes, there is no "includes", "imports" and even "modules", but there are libraries.

Also there is a workaround by using a closure. Bellow is a sample code. By executing the test function the log contains c.d. The idea is to have in all gs files a function started with init. In these functions all global variables are instanced. The anonymous closure is executed during the Code.gs file instancing and calls all "init" functions of all gs files.

Code.gs

var c;

function callAllInits_() {
  var keys = Object.keys(this);
  for (var i = 0; i < keys.length; i++) {
    var funcName = keys[i];
    if (funcName.indexOf("init") == 0) {
      this[funcName].call(this);
    }
  }
}

(function() {
  callAllInits_();
  c = { value : 'c.' + d.value };
})();

function test() {
  Logger.log(c.value);
}

d.gs

var d;

function initD() {
  d = { value : 'd' };
};
megabyte1024
  • 8,482
  • 4
  • 30
  • 44
  • Megabyte1024! Isn't Javascript just delightfully rich! I'm faced with an apparent "chinese puzzle" and then someone like you comes along with a perfect gem of a solution. Really nice. Thank you. – Martin Bramwell Oct 05 '12 at 10:40
  • @Hasan. Yes, JS is a very interesting language and find it very flexible. – megabyte1024 Oct 05 '12 at 11:00
  • was racking my brain on how to get this to work, thank you! the library seems to include via `FILES` (?), but this works well enough. I ended up calling the initializers in `onOpen`, which seems to work too. – drzaus Sep 13 '13 at 21:35
  • I think the loop that checks for functions starting with "init" should also test `typeof this[ funcName ] == "function" ` because the Object.keys also includes all the variables. – JohnRC Nov 16 '18 at 17:03
4

I tackled this problem by creating a class in each file and making sure that each class is instantiated in the original Code.gs (which I renamed to _init.gs). Instantiating each class acts as a form of include and makes sure everything is in place before executing anything.

_init.gs:

// These instances can now be referred to in all other files
var Abc  = new _Abc();
var Menu = new _Menu();
var Xyz  = new _Xyz();
var Etc  = new _Etc();

// We need the global context (this) in order to dynamically add functions to it
Menu.createGlobalFunctions(this);

function onInstall(e) {
  onOpen(e);
}

function onOpen(e) {
  Menu.build();
}

And classes usually look like this:

menu.gs:

function _Menu() {
  this.build = function() {
    ...
  }

  ...
}
Marco Roy
  • 4,004
  • 7
  • 34
  • 50
  • 1
    Thanks for taking the time add your solution as well. It's so long since I looked at that, that I can't recall enough about it to know whether you've advanced on @megabyte1024's correct answer. (I no longer work with Google's stuff -- it's too much like being a flea on the back of an elephant that has a good scratching post nearby.) – Martin Bramwell Nov 03 '15 at 21:42
  • 1
    I use a similar approach, although not in relation to menus. The advantage over @megabyte1024;s answer is that having an object corresponding to each gs file (in my thinking a "module") avoids the possible clashes of variable names in the global name space, and also makes explicit when calling a function in a module, in that the function name must be prefixed by the module name, as in `Menu.build();` above. – JohnRC Dec 05 '18 at 11:20
2

If you have more than one level of inheritance, you need to give the init functions names like init000Foo, init010Bar, and init020Baz, and then sort the init functions by name before executing. This will ensure init000Foo gets evaluated first, then Bar, then Baz.

function callAllInits() {
  var keys = Object.keys(this);
  var inits = new Array();
  for (var i = 0; i < keys.length; i += 1) {
    var funcName = keys[i];
    if (funcName.indexOf("init") == 0) {
      inits.push(funcName);
    }
  }

  inits.sort();
  for (var i = 0; i < inits.length; i += 1) {
    // To see init order:
    // Logger.log("Initializing " + inits[i]);
    this[inits[i]].call(this);
  }
}
zanerock
  • 3,042
  • 3
  • 26
  • 35
1

The other answers (i.e., don't write any top-level code which references objects in other files) describe the ideal way to avoid this problem. However, if you've already written a lot of code and rewriting it is not feasible, there is a workaround:

Google App Script appears to load code files in the order they were created. The oldest file first, followed by the next, and the most recently created file last. This is the order displayed in the editor when "Sort files alphabetically" is unchecked.

Thus, if you have the files in this order:

  • Code.gs
  • 1_File.gs (depends on 0_File.gs)
  • 0_File.gs

An easy fix is to make a copy of 1_File.gs and then delete the original, effectively moving it to the end of the list.

  1. Click the triangle next to 1_File.gs and select "Make a copy"
    • Code.gs
    • 1_File.gs
    • 0_File.gs
    • 1_File copy.gs
  2. Click the triangle next to 1_File.gs and select "Delete"
    • Code.gs
    • 0_File.gs
    • 1_File copy.gs
  3. Click the triangle next to 1_File copy.gs and select "Rename", then remove the " copy" from the end.
    • Code.gs
    • 0_File.gs
    • 1_File.gs

Now 0_File.gs is loaded before 1_File.gs.

benzado
  • 82,288
  • 22
  • 110
  • 138
  • 1
    and if you want to inspect the order, see the "script" object in the debug watch window upon breaking. – toddmo May 18 '20 at 23:38
  • 1
    It looks like they've added the option to reorder your scripts in their editor and that actually enforces the load order of the scripts. I haven't found anything in the documentation yet but experimented with it a bit and it seems like it works ok. So I think your solution is the most sensible way to do this and with this new feature in the apps script editor, you don't need to manually recreate the files. I have no idea what happens when you push the code with clasp through. – egst Mar 19 '21 at 14:08
0

This works for me as of December 2021. Quite likely, the other answers are outdated.

You can easily fix this. When you look at the scripts in the "Files" section of the web editor, you see they have an order. Files are evaluated in the order they appear there. Clicking on the three dots to the right of a file name brings up a menu that allows you to move a file up or down.

Lorenz
  • 1,263
  • 9
  • 20
  • This isn't a great solution solution. Someone else could change the order of the files without realizing it will break the project, and it's not immediately obvious what they did wrong. – Marco Roy May 03 '23 at 23:07
-1

There is no such order in Google Apps Script. It purely depends on where you have these objects declared and how your function is invoked. Can you explain a bit about how and when your Logger.log() code will be invoked. Also, when do you declare your objects objA and objB ? These will help us provide a better answer

Srik
  • 7,907
  • 2
  • 20
  • 29
  • Hi Srik. Thanks for taking an interest. Logger.log() is in the original code.gs. I had three files in total and played only with the order of 0_File & 1_File. Megabyte1024 has very thoroughly answered my question. – Martin Bramwell Oct 05 '12 at 10:31
  • There is indeed an order. If you break and inspect the "script" object in the watch window, you will see the entries in the order GAS sees them. – toddmo May 18 '20 at 23:37
-2

here is how I would do this...

main

function include(filename) {
  return ContentService.createTextOutput(filename);
}

function main() {
  include('Obj A');
  include('Obj B');
  Logger.log(ObjA.sayName + " : " + ObjB.sayName);
}

Obj A

var ObjA = new Object();
ObjA.sayName = " I'm A ";

Obj B

var ObjB = new Object();
ObjB.sayName = "[" + ObjA.sayName + "]";
Gene
  • 114
  • 1
  • 1
  • 6
  • This does not seem to work. I have tried to implement it but just get the error "ObjA is not defined". Checking the documentation for `createTextOutput()` does not give any indication that the function can be used in this way. Could you explain how you managed to get this to work? – JohnRC Dec 05 '18 at 11:09