Race conditions with enyo router

I ran into a little problem today with a custom enyo router with a slightly different regexp for tokens in dynamic routes.

Since the token regexp is a private protected variable inside a closure, I cannot simply override it by extending the enyo router. As a result I had to clone the native router. At first this seemed harmless apart from being a little fiddly, but today I ran into an interesting side effect.

The closure that contains the definition of the enyo.Router kind contains an enyo.ready callback that tells enyo.dispatcher to listen to the hashchange event on the window object. This essentially means binding the hashchange event happens asynchronously, leaving room for subtle race conditions.

To be more specific, when using the native enyo.Router this doesn't appear to be cause any problems, because the router is part of the enyo kernel. This ensures that router initialization and application start-up take place in the following order:

  1. enyo/kernel/Router.js file gets loaded
  2. The IIFE it contains gets executed, registering an enyo.ready callback
  3. enyo.ready triggers it's callbacks
  4. enyo.dispatcher gets instructed to listen for the hashchange event
  5. The application gets started.

However, now that I have been forced to create an entirely new router, rather than extending the original one, the new router is no longer part of the enyo kernel and thus no longer part of the kernel package. Instead it becomes part of the application package. This alters the order in which router initialization and application start-up take place:

  1. enyo/kernel/Router.js file gets loaded
  2. The IIFE it contains gets executed, registering an enyo.ready callback
  3. enyo.ready triggers it's callbacks
  4. The application gets started.
  5. enyo.dispatcher gets instructed to listen for the hashchange event
Note how the hashchange event gets bound after the application has already started. In my case, the router is triggered in enyo.Application.start, which means the hashchange is ignored until after myApp.Router.trigger has alreadu changed the window.location.hash.

Allthough I can work around this issue, it does make extending and customizing enyo's router a bit fiddly and I'm not sure that's really necessary... A couple of things come to mind that could be implemented to make enyo's router more flexible:

  • Set the token regexp on the enyo.Router kind, rather than as a private protected variable.
  • Implement the global hash change handler as a public static method rather than private protected method, so custom routers can still use it.
  • The prepare method also doesn't need to be private

Is this something the enyo team would be willing to look into?

Comments

  • Very interesting, do you mind sharing what your customized regexp is and/or a sample path you're trying to handle? Thanks!
  • Sure. Currently I've changed the token regexp from: /\:[a-zA-Z0-9]*/g to: /\:[a-zA-Z0-9-_]*/g. Nothing awfully exotic, it just allowed a bit more freedom to naming the tokens in a path.

    I find foo/:bar_and_baz/:bar_id/:baz_id just reads a little easier than foo/:barandbaz/:barid/:bazid

    The more important modifications I've made to the router are the regexp's to extract the value of a token. I've modified the regexp that extracts the token's value from a url path from: ([a-zA-Z0-9-]*) to: ([a-zA-Z0-9-_.!~*'()%+]*)
    This allows me to handle uri encoded values (as implemented by encodeURIComponent) in a dynamic routes and decode them in enyo.Router._handleDynamic).

    My use-case for this was a search feature, such that routes like search/:query can be used to match paths like these:

    - "search/john%20doe" => query = "john doe"

    - "search/cartman%20awesome-o" => query = "cartman awesome-o"

    - "search/M%26M's" => query = "M&M's".

  • Now that I'm looking through the code again, I noticed another thing.enyo.Router has some public methods, such as enyo.Router.trigger and enyo.Router.location that make use of private protected methods such as prepare and hashDidChange. So even if the token regexp doesn't strictly *need* to change, these private methods and variables still make it difficult to customize the router without creating a new IIFE with copies of these private methods and variables, creating the race-condition I mentioned as a side-effect.
  • Thanks for the detailed write-up @ruben_vreeken‌, we'll take a close look at this.
  • No problem. You can drop me a line any time if you need more info, details, feedback etc.
Sign In or Register to comment.