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
});
}
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.
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");
},
});
},
});
? It worked! 8 lines of code reproduce the bug. ?
And yes, both requests happen without error.
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");
});
});
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!
Continue reading about I broke AJAX in Chrome 52 ?
Semantically similar articles hand-picked by GPT-4
- JS object optimization bug in Chrome 52
- JavaScript can fetch() now and it's not THAT great
- How to waste hours of life with fetch() and a bit of brainfart
- A promises gotcha that will catch you out
- A lesson about client-side templating
Learned something new?
Read more Software Engineering Lessons from Production
I write articles with real insight into the career and skills of a modern software engineer. "Raw and honest from the heart!" as one reader described them. Fueled by lessons learned over 20 years of building production code for side-projects, small businesses, and hyper growth startups. Both successful and not.
Subscribe below 👇
Software Engineering Lessons from Production
Join Swizec's Newsletter and get insightful emails 💌 on mindsets, tactics, and technical skills for your career. Real lessons from building production software. No bullshit.
"Man, love your simple writing! Yours is the only newsletter I open 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. 👌"
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 ❤️