0

I'm trying to create a very basic client-side router using page.js. The idea being that when you visit a route, if you try and use a render method, then the render method will find the view you pass to it (by retrieving it from the server if necessary), then interpolate the view using lodash templates and finally display it to the user. However, I'm experiencing a weird phenomenon. Despite the fact the below example works absolutely perfectly, I am seeing this in my Chrome network log:

Chrome Network Log

(The image above is quite small. Here is the link: https://i.stack.imgur.com/qyhP6.png)

Now, I am firing off a request to /templates/index.html and I can verify that both of these requests are representing a XHR to the exact same location (that is, they both are pointing at /templates/index.html). Here is my router code (yes, I appreciate it needs refactoring):

var page = require('page')
  , home = require('./home')
  , lodashTemplates = require('lodash.template');

module.exports = function() {
  // refactor
  var routeContainer = document.querySelector('[data-container]');
  // TODO: Refactor this to somewhere else
  var render = (function() {
    var viewCache = new Map();

    return function render(viewName, locals, done) {
      if(viewCache.get(viewName) === undefined) {
        // cache miss - try and get the template
        var request = new XMLHttpRequest();
        request.open('GET', '/templates/' + encodeURIComponent(viewName) + '.html');
        request.onreadystatechange = function() {
          if(request.readyState === 4) {
            switch(request.status) {
              case 200:
                viewCache.set(viewName, lodashTemplates(request.response));
                // once we have cached the view, invoke ourselves..
                // this could probably be coded better
                render(viewName, locals, done);
                return;
              case 404:
              case 500:
                throw new Error('unable to retrieve view from server: '+viewName);
            }
          }
        };
        request.send();
        return;
      }

      // Do this elsewhere..
      var view = viewCache.get(viewName);
      var interpolated = view(locals);
      routeContainer.innerHTML = interpolated;
    };
  }());

  var thisContext = {
    render: render
  };

  // bind home.index to thisContext which gives it access to the render method
  // this should ideally be abstracted away but it will do for proof-of-concept
  page('/', home.index.bind(thisContext));

  // Always leave this line at the bottom, it is required to activate the routes.
  page({ hashbang: true });
};

home.index - this is bound to the thisContext in the above sample

  function index(context, next) {
    this.render('index', { foo: 'bar' });
  }

This utilizes Browserify for the require, lodash.template (npm) and page (npm) to achieve it's goal. As mentioned before, the result is being displayed correctly, which is why I am confused as to why this error is being shown. Interestingly, this issue only pops up in Chrome - it does not occur however during private browsing on Chrome nor does it occur in Chrome Canary.

Nagama Inamdar
  • 2,851
  • 22
  • 39
  • 48
Dan
  • 10,282
  • 2
  • 37
  • 64
  • If you add any console logging to the script, is it calling `render` more than once? – Kevin B Apr 28 '15 at 15:33
  • Have you checked your code in another browser like Firefox? If this issue is only being caused in Chrome, then it might be a chrome extension issue? – Hassaan Apr 28 '15 at 15:35
  • @KevinB yes, it is definitely calling it more than once (take a look at line 24). However it will only reach this if `viewName` is not a key in the `viewCache` - which it is. – Dan Apr 28 '15 at 15:36
  • @Hassaan I've tested it in Chrome Canary and it does not occur there, and I've also tested it in private browsing in Chrome (again no effect). Chrome Canary does not have my addons. This could potentially be related to AdBlock, but I do have AdBlock enabled in private browsing. I have no other plugins. – Dan Apr 28 '15 at 15:37
  • There are very few occasions where requests can be canceled: user pressing esc, an image being removed from the dom while it's loading, an iframe src changing while it's still loading, or leaving the page while an ajax request is currently in progress, and all of those can be pretty much ruled out in your case. My guess is it's an extension acting up. – Kevin B Apr 28 '15 at 15:46
  • I will disable all extensions and give it a whack. – Dan Apr 28 '15 at 15:47
  • This does not appear to have fixed the problem. :( – Dan Apr 28 '15 at 15:47
  • And adding in console.log does infact only get called twice? not 3-4 times? (though even then that shouldn't cause a canceled request..) – Kevin B Apr 28 '15 at 15:48
  • What is causing this code to run in this case? just viewing the page? or after clicking a link. – Kevin B Apr 28 '15 at 15:49
  • Placing `console.log` inside the branch that retrieves the view (and thus calls `new XMLHttpRequest()`) indicates that the constructor is only invoked once, yes. – Dan Apr 28 '15 at 15:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/76468/discussion-between-dan-pantry-and-kevin-b). – Dan Apr 28 '15 at 15:50

1 Answers1

0

This issue is due to a Chrome "feature" which occurs when you have the debug console open. It has already been reported on SO here. It has also been reported on Chromium's issue tracker here where Google won't be fixing it because it is already fixed in the latest Canary version.

I will just reiterate the answer given in the previous SO question by Gabriel here so make sure to upvote there too:

To make your code work always in debug mode, I found two options:

  1. Use onload to verify the success of an XMLHTMLRequest W3C Spec

    client.onload = handler;

    This version will not work in IE8. It must be a browser that implements the XMLHttpRequestEventTarget interface

  2. Remove the onreadystatechange handler as soon as you have a DONE 200 state.

    client.onreadystatechange = function() { // check if request is complete if (this.readyState == this.DONE) { if (this.onreadystatechange) { client.onreadystatechange = null; handler(); } } };

Community
  • 1
  • 1
Hassaan
  • 932
  • 8
  • 16
  • I have dev tools open in private browsing as well. My code works perfectly, there is just a weird residual cancelled request. I'm not entirely sure this is the correct answer. – Dan Apr 28 '15 at 16:01
  • See if the issue goes away if you use `onLoad`. Google might have fixed the issue in Incognito mode but not in normal mode since it was reported? – Hassaan Apr 28 '15 at 16:06