It’s taken me a bit of time to wrap my head around the issues here, but I think I have it clear enough to articulate the issues I’m bumping into and possibly propose some solutions.
There are two different scenarios related to circulars; debug builds and release builds. Debug Builds: I’ve run into two situations where I’ve had errors in the debug build due to circularities: 1. I’m using PureMVC (which ports to FlexJS as-is without a hitch by including the source). PureMVC has a Facade class which is a Singleton that is used as a go-between to get Proxies and Mediators as well a notification hub. In a number of places in my code I had some code which looks like this: private static var myProxy:SomeProxy = ApplicationFacade.getInstance().retrieveProxy(SomeProxy.NAME) as SomeProxy; The class which calls this is being loaded before ApplicationFacade.js, so ApplicationFacade is undefined when the above code is run. 2. I have a situation where the goog.requires are such that three of my sub-classes are being loaded before their super-classes. I’m not completely sure on exactly which circularity is causing this (more on that later), but the symptom of this problem is that when goog.inherits(MySubClass, MySuperClass); is called, MySuperClass is not yet defined. I resolved situation #1 (at least for the debug), by removing “static”. This moves the definition into the constructor, so it’s only evaluated when the class is instantiated — at which time, ApplicationFacade is already loaded. If we only need to deal with debug builds, both of these issues could be handled with initialization functions. Instead of this: /** * @constructor * @extends {Super} * @param {Holder} page */ Sub = function(page) { com.printui.model.vos.TextFrameVO._init_(); //do stuff... Sub.base(this, 'constructor', page); }; goog.inherits(Sub, Super); Sub.proxy = org.apache.flex.utils.Language.as(ApplicationFacade.getInstance().retrieveProxy(SomeProxy.NAME), SomeProxy); We could do this: /** * @constructor * @extends {Super} * @param {Holder} page */ Sub = function(page) { Sub._init_(); //do stuff... Sub.base(this, 'constructor', page); }; Sub._init_ = function(){ goog.inherits(Sub, Super); Sub.proxy = org.apache.flex.utils.Language.as(ApplicationFacade.getInstance().retrieveProxy(SomeProxy.NAME), SomeProxy); Sub._init_ = function(){} } This would cause class variables and inheritance setup to run the first time a class is instantiated and not when the script is first evaluated. Sub._init_ = function(){} at the end of the function redefines the function to do nothing after the first time it’s run, so class initialization would only happen once no matter how many times you instantiate the class. After writing this, I realized that the above suggestion is actually flawed for static classes which are not instantiated because the init function would never be called. I think the goog.inherits should be fine because inheritance only matters on classes which are instantiated. However, a better solution would probably be to call init() on all classes after they are loaded. As I understand it, the problem with my suggestion is that it does not address limitations in the Google Closure Compiler. This brings us to release builds. Release Builds: There’s many kinds of circularities, I’m I’m not clear on which ones cause problems. Alex wrote an explanation on the wiki,[1] which is a good starting point, but there’s still questions I have. I will list the ones that I’ve thought of: 1. A extends B and B extends A. This is not supported by ActionScript, so it’s a non-issue. 2. Super classes which have a reference to the sub class as mentioned in the wiki. 3. Classes which are unrelated in hierarchy, but reference each other. 4. A circular chain of class references. (i.e. a uses b which uses c which used d which uses a) 3. public static variables 4. private static variables 5. public variables 6. private variables 7. function parameters of all four types 8. function return types. 9. interfaces. To make this a bit more concrete, I have a number of known circularities in my code: 1. I have a sub class which calls a super method to take care of most if the object setup. This setup is identical for all sub classes with the exception of on piece which should be not done for a specific sub, so in the super class I have a line of code if(this is Sub){//don’t do it}. Getting rid of this line of code is doable, but would require indirection which I’d rather avoid. (but not too big a deal) 2. I have a “page” class which keeps track of all objects that it contains. The object classes each have a reference to their containing pages which is needed to do all kinds of calculations. I think this is a totally valid design, and doing it any other way would be very messy and make it very difficult to follow the code flow. This exact situation is mentioned in the closure lib Google Group.[2] 3. I probably have lots of chained dependencies — especially with regard to ApplicationFacade. I don’t know the best way to fix my problems with things as they stand. There were two suggestions to problem #2 on the Google Group.[3] One way to put the classes in the same file, and the second was to use interfaces. My question is whether interfaces would solve the problem: public interface IPage{ function get backgroundObject():PageItem; } public class Page implements IPage{ var pageItems:Vector.<PageItem> = new Vector.<PageItem>(); pageItems.push(new PageItem()); pageItems.push(new PageItem()); public function get backgroundObject():PageItem{ return pageItems[0]; } } public class PageItem{ private var _page:IPage; public function PageItem(page:IPage){ _page = page; } public function get background():PageItem{ return _page.backgroundObject; } } Page needs the interface IPage (or does it?) which needs PageItem, which needs IPage which needs PageItem ad infinitum. A bigger question I have is related to the first suggestion (i.e. including them in the same file). That makes me question whether we need good.provide and goog.require at all for the release build. For debug, we definitely want the files separated to identify code during debug. But for release, it seems to me that Falcon can simply combine all the required files into a single monolithic file containing everything. That should eliminate the need to use goog.provide and goog.require being that they are only used (AIUI) for goog to know which files to load. I see two ways this could be accomplished: 1. It could strip out all good.provides and good.requires fro the debug build and combine the files. 2. It could rebuild from source without goo.provide and goog.require at all. (It is possible that I’m not understanding what goog.provide does and it’s still needed, but I’m pretty sure I’m right about goog.require.) Wow. This was a long email, but I think this is an important topic. Thoughts? Harbs [1] https://cwiki.apache.org/confluence/display/FLEX/Circular+Dependencies [2]https://groups.google.com/d/msg/closure-library-discuss/ZfC1xKIs-NA/vjnhA4llbZsJ [3]https://groups.google.com/d/msg/closure-library-discuss/ZfC1xKIs-NA/BtEzF7Ow4R4J On Jul 18, 2016, at 9:30 PM, Alex Harui <aha...@adobe.com> wrote: > > > On 7/17/16, 11:45 PM, "Harbs" <harbs.li...@gmail.com> wrote: > >> The issue should be clear. >> >> If MySuperClass.js has not yet been loaded when MySubClass.js is loaded, >> goog.inherits(MySubClass, MySuperClass); which is in MySubClass.js will >> cause an error. > > Harbs, if our code normally had problems loading subclasses before base > classes, I doubt any of our examples would run. There should be a > goog.require for the base class at the top of the subclass's file and that > should load the base class before the goog.inherit runs. There is > something unique about your scenario. We need a test case in order to > determine what it is. > >> >> This code is being added to the JS file right after the constructor is >> being defined, but it’s not wrapped in an initializer function so it’s >> evaluated as soon as the JS file is loaded. >> >> If it would be wrapped in an init function, it could be evaluated only >> after all files are loaded, so all classes would be defined. >> >> We’ve been spending the better part of the last day trying to distill >> this down to a test case which causes the compiler to load the classes in >> the wrong order. We’re getting closer, but we’re not there yet. Even if >> we find the specific root cause in my case for the files to be loaded in >> the wrong order, I still think it’s a design flaw which should be fixed. >> The order of loading files should not matter. > > The order of loading files currently matters. IMO, it would not be worth > it to make it not matter. Google thinks it should matter otherwise they > wouldn't have created the goog.require system and deps.js. > > Thanks, > -Alex >