Skip to content
Swizec Teller - a geek with a hatswizec.com

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!

Did you enjoy this article?

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

Learned something new?
Want to become a high value JavaScript expert?

Here's how it works 👇

Leave your email and I'll send you an Interactive Modern JavaScript Cheatsheet 📖right away. After that you'll get thoughtfully written emails every week about React, JavaScript, and your career. Lessons learned over my 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

Start with an interactive cheatsheet 📖

Then get thoughtful letters 💌 on mindsets, tactics, and technical skills for your career.

"Man, love your simple writing! Yours is the only email I open from marketers and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"

~ Ashish Kumar

Join over 10,000 engineers just like you already improving their careers with my letters, workshops, courses, and talks. ✌️

Have a burning question that you think I can answer? I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

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

Curious about Serverless and the modern backend? Check out Serverless Handbook, modern backend for the frontend engineer.

Ready to learn how it all fits together and build a modern webapp from scratch? Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

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 ❤️