3

I got "Maximum call stack size exceeded." error when using Array.apply() to convert a large Uint8Array to an Array.

I found that apply() passes my parameter array as arguments in Array's constructor and function's arguments are stored in stack. Therefore, if I pass a large array to apply(), it'll exceed the stack.

After some searching, I changed to use Array.from() and it solved my issue.

I read the algorithm in ECMAScript but I can't understand it. So, could someone tell me the difference between apply() and from()?

6
  • Not sure if this is a duplicate of that question. This question is asking about the difference between these two, which is not what the other question is asking, nor is it addressed in any of the answers Commented Feb 20, 2020 at 11:28
  • Do you also get an error if you do Array(...yourArray) ? Or maybe that doesn't work with typed arrays. Commented Feb 20, 2020 at 11:31
  • @FelixKling You mean something like this Array(new Uint8Array(data)). It can run without error but it just created an array that has 1 Uint8Array element. Btw, I got the answer below, thank you. Commented Feb 21, 2020 at 1:26
  • No, I really meant Array(...data).... using argument spread, the modern alternative to .apply. But given the explanation it should result in the same issue. Commented Feb 21, 2020 at 7:11
  • @FelixKling I tried using argument spread and it still caused the error RangeError: Maximum call stack size exceeded as expected Commented Feb 24, 2020 at 1:43

1 Answer 1

3

Array.apply calls Function.prototype.apply with a calling context (a this value) of Array. If the second parameter is an array-like object, it will call the Array constructor with all of the elements of that array-like object as arguments. Eg:

Array.apply(null, [1, 2, 3, 4])

results in, and is equivalent to

Array(1, 2, 3, 4)

But argument lists have a size limit. It depends on the engine, but it looks like you probably shouldn't try to pass more than 10,000 arguments or so.

In contrast, Array.from invokes the iterator of the array-like object. If the iterator doesn't exist, but the object has a length property, it will iterate from 0 up to the value of the length - 1 and create an array from those values. Here, since a Uint8Array has an iterator, that iterator is what gets invoked when you use Array.from.

Iterators don't have the same sort of size limit that argument lists have (unless they're spread into an argument list, or something similar). The iterator's .next method is called, returning a value to put into the array, until the iterator is exhausted. It's pretty similar to the following:

const arr = [0, 1, 2];

// Create a new array by iterating through arr's iterator
// just like Array.from is doing:
const newArr = [];
const iterator = arr[Symbol.iterator]();
let iterObj = iterator.next();
while (!iterObj.done) {
  newArr.push(iterObj.value);
  iterObj = iterator.next(); 
}
console.log(newArr);

There are no limits on how long the iterator may be, either in the above code, or in Array.from.

So, if the iterable or array-like object you have is very large, constructing an array from it with Array.apply (or by spreading into the Array constructor) may throw an error, whereas using Array.from will not.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.