JavaScript | Web API:ストレージ系 API - 同期 API の特性

JavaScript JavaScript
スポンサーリンク

「同期 API」ってそもそも何者か

まず言葉の整理からいきます。
同期 API(synchronous API)というのは、呼び出した瞬間に処理が終わるまで、そのスレッドを止めてしまうタイプの API のことです。

JavaScript(ブラウザ)の世界で言うと、「メインスレッド」が止まります。
メインスレッドが止まるとどうなるかというと、

画面の再描画が止まる
ユーザーのクリックや入力への反応が遅れる
アニメーションがカクつく

つまり、「ユーザーから見ると固まったように見える」状態になります。

Web Storage(localStorage / sessionStorage)は、まさにこの 同期 API です。


Web Storage が「同期 API」であるという事実

setItem / getItem はその場で完了する

localStorage / sessionStorage の代表的なメソッドはこうです。

localStorage.setItem("key", "value");
const v = localStorage.getItem("key");
JavaScript

これらは Promise を返しません
await もいりません。
呼び出した瞬間に、その場で処理が完了します。

これは一見「楽でいいじゃん」と感じるかもしれませんが、
裏側ではこういうことが起きています。

setItem
ストレージに書き込むまで、メインスレッドをブロックする

getItem
ストレージから読み出すまで、メインスレッドをブロックする

つまり、重い処理を同期 API でやると、その間 UI が止まる ということです。

小さいデータなら問題になりにくい

現実的には、
「小さな文字列をちょっと保存する」程度なら、
ほとんどの環境で体感的な問題は起きません。

localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme");
JavaScript

このレベルなら、同期 API であることを意識しなくても、
ユーザー体験が壊れることはほぼありません。

問題になるのは、
「大量のデータ」「頻繁なアクセス」「重い処理」と組み合わせたときです。


同期 API の「怖さ」を具体的に感じてみる

例:巨大な JSON を localStorage に書き込む

例えば、こんなコードを想像してみてください。

function saveBigData(data) {
  const json = JSON.stringify(data);
  localStorage.setItem("bigData", json);
}
JavaScript

data が小さいうちは問題ありません。
でも、これが数万件のレコードを持つ配列だったらどうでしょう。

JSON.stringify 自体も重い処理です。
さらに、その巨大な文字列を localStorage に書き込む処理も同期です。

つまり、

JSON 変換が終わるまで
ストレージへの書き込みが終わるまで

その間、メインスレッドはずっとブロックされます。

ユーザーから見ると、

ボタンを押した瞬間、数秒間固まる
スクロールもできない
クリックも効かない

という「フリーズしたような」体験になります。

例:ループの中で何度も setItem する

もう一つ、よくある危険パターンです。

function saveItems(items) {
  items.forEach((item, index) => {
    localStorage.setItem(`item:${index}`, JSON.stringify(item));
  });
}
JavaScript

100 件くらいならまだしも、
1000 件、10000 件と増えていくと、
setItem の呼び出し回数もそのまま増えます。

しかも全部同期です。

つまり、

forEach のループが終わるまで
その中の JSON.stringify が全部終わるまで
その中の setItem が全部終わるまで

メインスレッドはずっとブロックされ続けます。

「同期 API をループの中で大量に呼ぶ」
これは、UI を固める典型パターンです。


なぜ Web Storage はあえて「同期」なのか

シンプルさと用途の想定

Web Storage が設計されたとき、
想定されていた用途は「軽量なキー・バリューの保存」でした。

ちょっとした設定
小さなキャッシュ
簡単な状態の保存

こういった用途なら、
同期 API のほうがシンプルで扱いやすい、という判断があります。

await もいらない
Promise もいらない
try/catch だけでエラー処理ができる

「軽い用途に限るなら、同期のほうが楽」という側面は確かにあります。

だからこそ「使い方を間違えると痛い」

ただし、
ブラウザやアプリの規模が大きくなり、
「なんでも localStorage に突っ込む」ような使い方をすると、
同期 API であることが一気に足を引っ張ります。

ここが重要です。

Web Storage は「軽い用途向けの同期ストレージ」
重い用途に使うと「同期であること」がそのままデメリットになる

同期 API の特性を知らないまま使うと、
「なんかよく分からないけど UI がカクつく」
という状態を自分で作り出してしまいます。


同期 API を使うときにプロが意識していること

「どのタイミングで呼ぶか」を選ぶ

同期 API を使うときに一番大事なのは、
「どのタイミングで呼ぶか」を意識すること です。

ユーザー操作の真っ最中(ドラッグ中、スクロール中など)
アニメーションが動いている最中
頻繁に発火するイベント(inputscroll など)の中

こういう場所で重い同期処理をすると、
体感的なカクつきがすぐに出ます。

逆に、

ページ読み込み直後の一回だけ
ユーザーが「保存」ボタンを押したときだけ
あまり頻繁ではない操作のタイミング

などに限定すれば、
多少重くても「まあそういうもの」として受け入れられます。

プロは、
「同期 API をどこで呼ぶか」を設計の一部として考えています。

「どれくらいの量を扱うか」を制御する

もう一つのポイントは、
「一度に扱うデータ量を制御する」 ことです。

巨大な配列を丸ごと JSON にして一気に保存するのではなく、
そもそもそんなに大きなものを Web Storage に置かない設計にする。

どうしても大きくなるなら、

保存する項目を絞る
ID だけ保存して、詳細はサーバーから再取得する
古いデータを捨てて、最新の一部だけ保存する

といった工夫をします。

同期 API は「重くなればなるほど UI を止める時間が伸びる」ので、
「重くしない工夫」そのものが設計の一部 になります。


同期 API の特性を踏まえた「現実的な使い方」

Web Storage は「軽い設定・状態」の置き場と割り切る

実務的には、
localStorage / sessionStorage をこう割り切るのが現実的です。

ユーザー設定(テーマ、言語、表示オプションなど)
軽い状態(最後に開いていたタブ、フォームの下書きなど)
小さめの JSON データ

こういうものを保存するには、
同期 API であることはほとんど問題になりません。

逆に、

大量のログ
巨大なリスト
画像やバイナリを文字列化したもの

こういったものは、
そもそも Web Storage に向いていません。

そういう用途には IndexedDB などの非同期ストレージや、
サーバー側の保存を検討するフェーズです。

初心者として持っておいてほしい感覚

最後に、あなたの頭の中に置いておいてほしいのは、この感覚です。

Web Storage(localStorage / sessionStorage)は「同期 API」である
同期 API は、重い処理をすると UI を止める
小さなデータなら気にならないが、大きなデータ・頻繁な呼び出しで一気に問題化する
「どのタイミングで」「どれくらいの量を」扱うかを設計として考える
Web Storage は「軽い設定・状態の保存」に使うのがちょうどいい

おすすめの小さな実験として、
わざと大きな配列を JSON.stringify して localStorage に保存するコードを書いてみて、
そのときの UI の挙動(カクつき具合)を自分の目で確かめてみるといいです。

「同期 API は、重くすると本当に UI を止めるんだ」という体感が一度あると、
あなたの中で「どこで何を保存するか」の判断基準が、ぐっとプロ寄りになります。

タイトルとURLをコピーしました