あるとき突然、 JavaScript で次のエラーが発生・・・
1 2 3 |
Uncaught RangeError: Maximum call stack size exceeded at uint8ArrToStr (VM15192 App.js:129) at App.<anonymous> (VM15192 App.js:134) |
Maximum call stack size exceeded 、、、?
どうやらスタックオーバーフローしてしまった模様。
この解決策は意外と簡単だったので、
ここでは JSで Maximum call stack が出た時の対処法 をまとめました。
このページの目次
Maximum call stack の原因は apply だった...
今までこんなエラーが出てなかったけど、突然出てきたんです。
その原因を考えてみると apply を使ってたから
この applyメソッド は用心して使わないと、深刻なバグの温床になるかもしれません。
まず applyメソッド ですが、次のような引数を渡して使うものです。
構文
func.apply(thisArg, [argsArray])thisArg
この値は func 関数を呼び出す際に渡す this の値です。argsArray
1 つの配列風のオブジェクトであり、 func 関数が呼ぶことになる引数を列挙したものです。
可変長引数の関数に対して、配列で要素追加したいときとかに使えます。(例 : Array.push とか)
問題が起きるのは apply(thisArg, [argsArray]) に渡してる配列が巨大な場合です。
例えば実際にエラーが出たコードはこんな感じでした(一部簡易化)
▼ エラーが起きたコード例
1 2 3 4 5 |
/// サイズが1MBくらいのバイト配列 var uint8Arr = [100, 24, 121, ... 250, 28, 152]; /// バイト配列をBase64形式に変換する var encodedStr = String.fromCharCode.apply(null, uint8Arr); |
これは Uint8Array をBase64形式に変換しているコード
別に配列が 1KB とかだったらエラーは起きません。
でも 1MB とかの巨大な配列になると、
apply関数に渡せる引数の最大値を超えるからか、エラー発生してしまいます。
試しに apply に渡せる引数の最大値を調べてみた
そこで apply にどれだけ引数を渡せるかテストしてみることに・・
最新Chromeで試してみたところ、結果はこうなりました。
1.引数が 100000個(10万個)の場合
▼ テストコード
1 2 |
var uint8Arr = new Uint8Array(100000).fill(66) console.log(String.fromCharCode.apply(null, uint8Arr)); |
このコードは何の問題もなくクリア
2.引数が 125000個(12.5万個)の場合
▼ テストコード
1 2 |
var uint8Arr = new Uint8Array(125000).fill(66) console.log(String.fromCharCode.apply(null, uint8Arr)); |
これも問題なくクリア
3.引数が 126000個(12.6万個)の場合
▼ テストコード
1 2 |
var uint8Arr = new Uint8Array(125000).fill(66) console.log(String.fromCharCode.apply(null, uint8Arr)); |
Maximum call stack size exceeded...
この時点でアウト。少なくとも12.6万個以上の引数はダメみたい
より正確な値を調べると、125759個 がエラーの境界 と判明しました。
1 2 |
var uint8Arr = new Uint8Array(125759).fill(66) console.log(String.fromCharCode.apply(null, uint8Arr)); |
つまり引数に渡せるのは 125758個 までということ
もちろんこれは 最新版Chromeで調べた値です。
他ブラウザで調べてみたら、また違った結果が出てくると思います。
Maximum call stack の一番簡単な解決策
エラーの原因が【引数の渡し過ぎ】なら解決策も簡単です。
単純に apply に一度に渡す引数を減らせばいいだけ
たとえば先ほどの例、引数が多すぎるのが原因でした。
▼ エラーが出てしまうダメなコード
1 2 3 4 5 |
/// サイズが1MBくらいのバイト配列 var uint8Arr = [100, 24, 121, ... 250, 28, 152]; /// バイト配列をBase64形式に変換する var encodedStr = String.fromCharCode.apply(null, uint8Arr); |
この解決策は【小分けに分割する処理】に変えること。
▼ エラーを解決できたコードがコチラ
1 2 3 4 5 6 7 8 9 10 11 |
/// サイズが1MBくらいのバイト配列 var uint8Arr = [100, 24, 121, ... 250, 28, 152]; /// Base64変換を小分けにする const APPLY_MAX = 1024; var encodedStr = ''; for(var i = 0; i < uint8Arr.length; i+=APPLY_MAX){ encodedStr += String.fromCharCode.apply( null, uint8Arr.slice(i, i+APPLY_MAX) ); } |
こういう感じ。
少し面倒だけど、apply に渡す引数を 1024個 などに小分けすれば解決
あるいは1つずつ引数を渡すというのもアリかもしれません。
関数を apply から実行するときは要注意!
ということで、ここまでの内容を簡単にまとめ
- Maximum call stackの原因
ほとんどの場合は関数に引数を渡しすぎているため。特に apply 関数を使う場合、引数が多すぎるとスタックオーバーフローが起こりやすい
- 解決策は小分けに引数を渡すこと
たとえば配列を apply に渡すなら、1024個ごとなど小分けに渡せばOK。
以上、JavaScriptでの Maximum call stack エラーの原因でした。ではまた