Step 2: Browser & SPA
12. 画面がフリーズする…?
非同期処理 async/await で解決した話
iPhoneのHEIC画像変換中、ブラウザが全く動かなくなって焦った経験はありませんか?ユーザーの操作を妨げず、バックグラウンドで重い処理を走らせる「非同期処理」という概念の重要性についての備忘録です。
JavaScriptは一度に一つのことしかできない
サーバー側のマルチスレッド処理に慣れているエンジニアにとって、フロントエンド開発での最大の衝撃は「JavaScriptはシングルスレッドである」という事実かもしれません。ブラウザ上のJavaScriptエンジンは、基本的に一度に一つの作業しかこなせません。
例えば、猫の手ツールで「HEIC画像をJPEGに変換する」という計算負荷の高い処理を、メインの処理の流れ(スレッド)でそのまま実行してしまうとどうなるでしょうか。ブラウザはその計算に全力を注ぐため、ユーザーがボタンを押したり、画面をスクロールしたりといったUI操作をすべて無視してしまいます。これがユーザーに「サイトが固まった(フリーズした)」と感じさせる正体です。
非同期処理(Async/Await)による解決
この問題をスマートに解決するのが、JavaScriptの非同期処理です。特に現在のモダン開発において async/await 構文は欠かせません。
重い処理を非同期関数(async function)として定義し、その内部で待機が必要な処理に await を添えます。これにより、JavaScriptエンジンは「その処理が終わるのを待っている間、他のUI操作を処理する余裕」を持つことができるようになります。
Before/After:コードで見る違い
まず、UIをフリーズさせてしまう可能性がある「同期的な」書き方を見てみましょう。
// 良くない例:処理が終わるまでブラウザが固まる
function processImage(file) {
showLoading();
const converted = heic2any({ blob: file }); // ここで数秒間フリーズ
displayImage(converted);
hideLoading();
} 次に、async/await を使った「非同期な」書き方です。猫の手ツールでもこの手法を全面的に採用しています。
// 良い例:ブラウザの挙動を止めずに待機する
async function handleImage(file) {
showLoading(); // UIは即座に反応
try {
// await で変換が終わるのを待つ。
// この間、メインスレッドは解放され、ユーザー操作が可能です。
const converted = await heic2any({ blob: file });
displayImage(converted);
} catch (error) {
console.error("変換に失敗しました", error);
} finally {
hideLoading();
}
} 注意点:すべてが「魔法」ではない
非同期処理を使えばどんなに重い計算も完璧に解決する、というわけではありません。以下の点には注意が必要です。
- 計算そのものの負荷:
awaitは「待ち時間」を効率化しますが、CPUをフルに使う純粋な計算処理(暗号化や画像加工など)自体がメインスレッドを占有する場合、async/awaitだけではフリーズを防げないことがあります。その場合は Web Workers などの並列処理を検討する必要があります。 - エラーハンドリング:
awaitを使う際は、必ずtry/catchで囲む癖をつけましょう。非同期処理の失敗はキャッチし損ねるとアプリ全体のクラッシュに繋がります。
まとめ:優れたUXには「非同期」の理解が不可欠
UXを損なわずに重い処理をこなすには、非同期のコントロールが不可欠でした。インフラ開発における「非同期キュー」や「メッセージング」に近い感覚で理解できた時、JavaScriptの挙動がスッと腑に落ちました。猫の手ツールを「サクサク動くツール」にするために、この async/await は今やなくてはならない技術です。