Swizec Teller - a geek with a hatswizec.com

Senior Mindset Book

Get promoted, earn a bigger salary, work for top companies

Senior Engineer Mindset cover
Learn more

    I broke AJAX in Chrome 52 ?

    This strange bug from yesterday won’t let me sleep. Why does Chrome 52 sometimes behave like different instances of a class are the same object?

    Backbone’s fetch method triggers the bug. But maybe that’s not the real issue? I’d hate to submit a bug report to Chrome project only to be told “Fool, that’s a Backbone issue.”

    It’s terrifying to tell the Chrome team they made a mistake.

    This is what yesterday’s repro code looks like:

    var BugModel = Backbone.Model.extend({
      url: "bla.json",
    });
    
    let bug = new BugModel();
    bug.fetch({
      success: () => {
        console.log("fetch 1"); // prints
        doWeirdness(bug);
      },
    });
    
    function doWeirdness(bug) {
      let newBug = new BugModel({ id: 1 });
    
      console.log("about to re-fetch"); // prings
    
      newBug.fetch({
        success: () => console.log("fetch 2", newBug), // doesn't print
        error: () => console.log("error"),
      });
      newBug.fetch({
        success: () => console.log("fetch 3"), // prints
      });
    }
    
    2 fetches, 1 callback

    If you press Cmd+R, the bug happens. If you press Cmd+Shift+R, it does not. That’s a new clue that points at either Chrome’s speed optimizations, or worse, the network stack. Can we call it a network stack? I guess Chrome is almost an operating system at this point …

    Adding console.log(newBug == bug) prints false, which implies that Chrome does not think both instances are the same object. This invalidates my original hypothesis. ?

    So what does Backbone’s Model.fetch method do?

    fetch: function(options) {
          options = _.extend({parse: true}, options);
          var model = this;
          var success = options.success;
          options.success = function(resp) {
            var serverAttrs = options.parse ? model.parse(resp, options) : resp;
            if (!model.set(serverAttrs, options)) return false;
            if (success) success.call(options.context, model, resp, options);
            model.trigger('sync', model, resp, options);
          };
          wrapError(this, options);
          return this.sync('read', this, options);
        },
    

    A lot of this stuff is unnecessary in ES6, but Backbone is from the before times.

    We start with a default value for options - {parse: true}, then use the var = this trick because we don’t have arrow functions. Then we copy the options.success callback to a variable and define our own. You can think of it as a wrapper.

    Inside the success wrapper, we parse data returned from the server and set new values on our model. Then we trigger a sync event. This could be where the bug happens.

    Outside the wrapper, we defer to sync to actually talk to the server.

    If I copy this method to my own model definition, we can inspect where it fails.

    Success wrapper doesn't fire

    The success wrapper doesn’t fire. ?

    Let’s see what happens inside sync … ugh, it’s a long function. I’m not pasting it here. It does some setup, then defers to $.ajax to perform an ajax request to the server.

    Can we make the same bug happen without Backbone, then?

    $.ajax({
      url: "bla.json",
      complete: () => {
        console.log("done 1st request");
        $.ajax({
          url: "bla.json",
          complete: () => {
            console.log("done 2nd request");
          },
        });
      },
    });
    
    Bug without Backbone

    ? It worked! 8 lines of code reproduce the bug. ?

    And yes, both requests happen without error.

    Network calls do happen

    It might be safe to say that jQuery is battle tested enough that this couldn’t be a jQuery bug. But let’s try superagent to make sure. It’s a great library for making requests and it’s implemented independently of jQuery.

    Does the bug still happen?

    request.get("bla.json").end(() => {
      console.log("1st success");
      request.get("bla.json").end(() => {
        console.log("2nd success");
      });
    });
    
    Superagent repros too

    Yeeeeep.

    Now you might think: “A-ha! Every even Ajax call to the same URL fails.” I tried that, too -> it doesn’t. If you make requests in a loop, they all work.

    The bug only happens, if you make the same AJAX request in the callback. You can extend a chain like this forever:

    request.get("bla.json").end(() => {
      console.log("1st success");
      request.get("bla.json").end(() => {
        console.log("2nd success");
      });
    
      request.get("bla.json").end(() => {
        console.log("3rd success");
    
        request.get("bla.json").end(() => {
          console.log("4th success");
        });
      });
    });
    

    And it only prints the odd numbered console.logs.

    Guess it’s time to submit my first bug report to a big open source project. Yay I’m helping!

    Published on August 11th, 2016 in Front End, Technical

    Did you enjoy this article?

    Continue reading about I broke AJAX in Chrome 52 ?

    Semantically similar articles hand-picked by GPT-4

    Senior Mindset Book

    Get promoted, earn a bigger salary, work for top companies

    Learn more

    Have a burning question that you think I can answer? Hit me up on twitter and I'll do my best.

    Who am I and who do I help? I'm Swizec Teller and I turn coders into engineers with "Raw and honest from the heart!" writing. No bullshit. Real insights into the career and skills of a modern software engineer.

    Want to become a true senior engineer? Take ownership, have autonomy, and be a force multiplier on your team. The Senior Engineer Mindset ebook can help 👉 swizec.com/senior-mindset. These are the shifts in mindset that unlocked my career.

    Curious about Serverless and the modern backend? Check out Serverless Handbook, for frontend engineers 👉 ServerlessHandbook.dev

    Want to Stop copy pasting D3 examples and create data visualizations of your own? Learn how to build scalable dataviz React components your whole team can understand with React for Data Visualization

    Want to get my best emails on JavaScript, React, Serverless, Fullstack Web, or Indie Hacking? Check out swizec.com/collections

    Did someone amazing share this letter with you? Wonderful! You can sign up for my weekly letters for software engineers on their path to greatness, here: swizec.com/blog

    Want to brush up on your modern JavaScript syntax? Check out my interactive cheatsheet: es6cheatsheet.com

    By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️

    Created by Swizec with ❤️