Search code examples
javascriptclassnew-operatorextendssuper

super method undefined in class declaration


TL;DR - The super keyword in my class declaration is sometimes (but not always) undefined. How/why?

I have the following class declaration extending Array used for search result data from an API:

class SearchResult extends Array {
  constructor(resultArray) {
    super(...resultArray);
  }

  static createFromApiData(apiData) {
    const resultArray = apiData.filter(...).map(...);
    return new SearchResult(resultArray);
  }

  ...otherMethods

}

In my app this is initially called immediately on receiving the response from the API, like below:

apiCall().then((res) => {
  const searchResults = SearchResult.createFromApiData(res.data);
});

Creating a new SearchResult instance using this static method works correctly, returning what I want.

However, later in my app I need to create a SearchResult instance again, this time there is no need for an API call as I already have the resultArray data stored in a database. Therefore the second time around I try to create the instance of SearchResult using the new keyword like below:

const searchResults = new SearchResult(resultArray);

This time, using the new keyword the instance creation fails throwing the following error:

TypeError: undefined is not a function at new SearchResult

I know undefined in this context is referring to the super keyword but I can not understand why this is the case? How can the instance creation behaviour vary in these two cases? How can I fix it?


UPDATE

I've investigated further and found the following. My initial issue was caused because I was saving my custom SearchResult class instance to the database (dynamodb). Although SearchResult extended array, ddb saved it as a basic object, so on retrieval of the data from the database it came in basic object, not array form. This then threw an error when I attempted to spread the object inside inside the super function.

I resolved this by spreading my class instance in a new array when saving it to ddb:

db.save([...searchResults]);

This returned an array after database recall and could be successfully spread inside the super call.

However, this has lead to a new issue. Immediately after creating the new SearchResult instance I call a the following custom method on it:

nextItem() {
  this.splice(0, 1);
  return this[0];
}

This method then again calls super to access the splice method on Array. However, once again this fails throwing the same error as before:

{
  "errorMessage": "undefined is not a function",
  "errorType": "TypeError",
  "stackTrace": [
    "new SearchResult",
    "SearchResult.splice",
    "SearchResult.nextRecipe"
}

Once again this is referring to the spread operation inside super. This time super is being passed the number 1 as its sole argument. Where is is this 1 coming from and how can I fix it?


UPDATE 2

After updating the class method to us shift instead of splice the method now works correctly without errors. My problem is now effectively solved, however I would still appreciate insight on what the issue of super being called with 1 was about with splice. Why would these two array methods behave differently in this context.

nextItem() {
  return this.shift();
}

Solution

  • I know undefined in this context is referring to the super keyword but I can not understand why this is the case?

    Probably not. super refers to the internal unchangeable [[HomeObject]] property of the class, that gets set when the class gets declared, you can't modify that, you can only rewrite the class itself, e.g.:

     SearchResult = function() { undefined(); }
    

    But I guess you don't do that.

    But where does the error come from?

    Well there are two things happening in that line:

     super(...resultArray);
    

    One is the super() call and the other one is the spreading of the resultArray, which will create and consume an iterator of the resultArray which is barely equal to:

     resultArray[Symbol.iterator]()
    

    and if the iterator does not exist (because you accidently passed a non array to the constructor) it fails, saying that the iterator can't be called:

     var resultArray = {};
    
     resultArray[Symbol.iterator]()
     // equals:
     undefined()