今週の困ったことメモ。あまり解決してないけど書いておく。
[画面にデータが表示されない問題]
開発中のアプリケーションで、あるユーザでログインしたときだけ画面にデータが表示されないという問題が発生した。
開発者ツールを開くと、コンソールに次のようなエラーが記録されていた。
Uncaught (in promise) TypeError: e.reduce is not a function at a.mapData (xxx.js:4415:30)
コードを読むと、あるAPIのレスポンスが配列であることを想定して次のようなコードが書かれていた。
mounted() { Promise.all([ callApi("foo").then(data => this.foo = this.mapData(data, "hoge")), callApi("bar").then(data => this.bar = this.mapData(data, "fuga")), callApi("baz").then(data => this.baz = this.mapData(data, "piyo")), ]); }, methods: { mapData(data, key) { data.reduce((map, obj) => (map[obj[key]] = obj.name, map), {}) } }
問題があったのはこのうち、bazのAPIを呼んだ部分で、開発者ツールでmapData()に渡されたdata
の型を見ると、配列ではなく文字列になっていた。
reduce()関数はArray.prototype.reduce()で配列に生えているので、文字列型で利用できないのは仕方ない。
[axiosのissue]
callApi()の内部ではHTTPクライアントとしてaxiosというライブラリを利用して、サーバからデータを取得している。
ちょっと古いコードだったので、利用していたバージョンは2018年の0.18.0だった。
開発者ツールでサーバからのレスポンスを確認したが、レスポンス上は問題なく配列になっているように見えた。何も問題はなさそうに見える。ふと気になってレスポンスをPretty Printボタンで整形しようとしたところ、結果の表示にやたらと時間がかかる。よくよく確認してみると、レスポンスのサイズが5Mバイトとなっていた。
サイズがデカすぎるとレスポンスをうまくパースできないとかあるのか...?と思い、ググってみるとそれっぽいissueがあった。
JSON.parse()の例外を握りつぶしてしまっているというわけだ。
暫定的なものも含めて、解決策として以下のものが話し合われていた。
- APIの返すデータが不正なJSON文字列だったのを直した
- axiosもXMLHttpRequestもダメだけどfetchを使ったら問題なかった
- データがデカすぎると発生するのでページングを実装しろ
データ量の問題か?と思ったが、なんとか開発者ツールでHTTPレスポンスを取得して、直接JSON.parse()メソッドに渡してみると、SyntaxError: Unexpected token
のエラーが出ており、バックエンドのAPIが悪さをしているらしいことが分かった。
それはそれで別途直さないと...。
ちなみにこのissueは2018年8月にオープンされて、1年後の2019年8月に重複と言われてクローズされている。重複しているissueは以下の2015年のもの。
この修正は2021年にバージョン0.21.2でようやく対応され、オプションを設定すればJSONのパースエラーをaxios利用者がハンドリングできるようになった。
以下のように、axiosのインスタンスにsilentJSONParsing
オプションを渡すと例外をハンドリングできるようになるので、これでエラーの発生をUIで表現できるようになる。
await axios.get("https://run.mocky.io/v3/95e6ffc7-ac0c-46c2-aded-7149eeb2ef2d", { responseType: "json", transitional: { silentJSONParsing: false } }); SyntaxError: Unexpected token b in JSON at position 1 at JSON.parse (<anonymous>) at Object.transformResponse (axios2.js:1023) at transform (axios2.js:493) at Object.forEach (axios2.js:257) at Object.transformData (axios2.js:492) at onAdapterResolution (axios2.js:1120)