While looking into an issue with Microsoft Edge and the Facebook sdk a few weeks ago, we ran into a very common error: Cannot read property ‘length’ of undefined. In fact, this is the 6th most common error, affecting nearly 28% of accounts.

This error indicates that our code expects to have an object with a length property, but that object was not present. length is commonly used on string and array, but a custom object could also have this property.

This is a blocking error, and execution will stop. It is important to defensively typecheck data passed in from external scripts and systems before interacting with them.

Root Cause

This error can be thrown for a lot of reasons, as it is incredibly common to reference the length property of string or array during everyday development.

In our case, we were wrapping a JavaScript function to intercept fetch requests for instrumentation, and we had expected the url property as defined in the fetch specification would be a string.

The Facebook sdk didn’t call fetch with a string. They called it with a serializable object. 🤦‍♂️.

They had built an object similar to the nodejs Url class, which provides some easier access and utilities to build up a url string. The custom object could be serialized as a url with the implementation of a custom toString function. Here is a simplistic example:

fetch({
  protocol: "https",
  hostname: "example.com",
  path: "/foo",
  toString: function() {
    return `${this.protocol}://${this.hostname}${this.path}`;
  }
});
Serializable object passed to fetch

Passing a serializable object into fetch is not in the specification, however it seems to work in all the modern browsers.

Due to the nature of our wrapping of fetch, we assumed url to be a different type, and threw an error trying to get the length of something that wasn’t a string.

In my opinion, it would have been better to serialize their url object to a string literal before calling fetch, and not rely on undocumented behavior from the browsers. But you can’t rely on external teams to always make sound technical decisions.

How to Fix

Always defensively check data from external sources, which include api requests, network responses, and function calls from other scripts. If you accept a string value, this can take a few different forms:

  • A String Literal "like this"
  • A String Object new String("like this")
  • A Serializable Object { toString: () => "like this" }

Of course, it can also be null, undefined, or a totally different unsupported type as well. In most cases, we can simply coerce the value to a string, which will guarantee our string functions work.

Coercing the value to a string is not complicated, and you don’t have to check for all the variations it could be. Let the runtime do that:

function myFunc(unknownStringVar stringy) {
  var knownStringVar = "" + stringy;
  // go about your day
}
Coerce stringable arguments into string literals.

This will serialize string literals, objects, numbers, booleans, and serializable objects into a plain-old string literal that we can safely get the length.

JavaScript can fail in lots of interesting ways. Maybe you can’t get the length of undefined, or maybe that network request is sporadically failing in production. Front-end error monitoring from TrackJS gives your team insight into production problems, without needing to learn complex logging tools.