解決したい問題
勤務先のWebサイトで、他社製のスクリプトAの実行が完了する前に、自社製のスクリプトBが実行されるとIE11がクラッシュするバグが見つかりました。具体的には以下のように呼び出しています。
なお、A.jsの呼び出し用タグは他社からの指示で1文字たりとも変更できません。
<html>
<head>
<!-- 自社製スクリプト -->
<script src="./B.js"></script>
<!-- 他社製スクリプト -->
<script src="./A.js" async></script>
</head>
<body>
</body>
</html>
クラッシュする原因は「B.jsの作りに問題があり、その問題をIE11だけが踏み抜くから」なのですが、A.jsの実行が完了していればクラッシュは回避できるため、B.jsがA.jsの実行を待てば良いと思い、以下のように書換えました。
<html>
<head>
<!-- 他社製スクリプト -->
<script src="./A.js" async></script>
<!-- 自社製スクリプト -->
<!-- deferはasyncの完了も待ってくれることを期待した -->
<script src="./B.js" defer></script>
</head>
<body>
</body>
</html>
仕様を誤解していた箇所
deferはasyncの完了を待ってくれると勝手に思い込んでいたのですが、クラッシュしたりしなかったりする挙動になってしまい、状況が悪化しました。
WHATWGの規格書を読んでもどうしてもピンと来なかったため、検証用のコードを書くことにします。
挙動さえわかればいいのでコード自体はめちゃくちゃ適当です。色んな人から怒られそうなコードですね。
検証用コード
html
<html>
<head>
<script src="./first.js" async></script>
<script src="./second.js" defer></script>
</head>
<body>
</body>
</html>
asyncで実行するjs (first.js)
var i = 0;
for (i = 0; i < 500000000; i++) { }
console.log("first.js: " + i);
deferで実行するjs (second.js)
console.log("second.js: " + i);
検証結果
Chrome 80で検証しました。
どちらもdeferがついていれば想定通りfirst->secondの順で呼び出されますが、asyncとdeferだと必ずasync->deferになるわけではないのですね。
実際に検証した後に <script> タグに async / defer を付けた場合のタイミング - Qiita を読むと大変わかりやすかったです。というか最初からこちらの記事を読んでいれば無駄に検証しなくても良かったのでは…。
以下も検証しましたがやはりNGでした。10回に1回位はエラーが出ます。
<html>
<head>
<script src="./first.js" async></script>
</head>
<body>
<script src="./second.js"></script>
</body>
</html>
ということで、今の所思いつく解決策としては「A.jsの作成元にdeferで呼び出していいか確認する」か、「自社スクリプトB.jsを修正する」のどちらかになりそうです。思い込みは良くないですね。
フロントエンドは初心者なので頑張って勉強していきたいです。