Sassyブログ

好きなことで暮らしを豊かにするブログ

ブラウザやタブを閉じたり、画面内でコンテンツを切り替えたりするときに非同期処理をしたい場合はsendBeacon使いましょう

掲題の通りなのですが、ブラウザやタブを閉じるタイミングや画面内で別コンテンツに切り替える場合に
その時の状態のデータをWebAPIリクエストしたりしたい場合ってありますよね?

JavaScriptAjax通信なんかは基本非同期なので、これをそのままブラウザやタブを閉じるタイミングで行うと、
あれ?処理されてないんだけど何で?ってなるかと思います。

これはブラウザやタブを閉じる際にunloadというイベントが発生するんですが、

その中で非同期処理を行うと非同期処理が完了する前にページが破棄されてしまうので非同期処理がキャンセルされてしまうためです。

unloadイベントについてはこちら

developer.mozilla.org

これを解決してくれるのがsendBeaconというJavaScriptAPIです!

sendBeaconの仕様については以下を参照してください。

developer.mozilla.org

www.w3.org

使い方はaddEventListenerのコールバック関数内でsendBeaconを呼び出してあげればよいです。

  const handleUnload = () => {

    const body = new FormData();
    body.append('_method', 'PUT');
    body.append('csrf-token', csrfToken);
    body.append('key', value);
    navigator.sendBeacon(`${apiUrl}`, body);

  };

addEventListener('unload', handleUnload, false);

大まかにメリット / デメリットを挙げると以下です。

メリット

  • unloadタイミングでAPIリクエストを投げてもキャンセルされない
  • 次のページの読み込みへの影響がない
  • 従来の実装よりも簡単に記載ができる

デメリット

  • 送信データサイズに限りがある
  • ヘッダー設定できない
  • POSTリクエストしか投げられない

ここで補足しておくとsendBeaconはunload時などに非同期のAPIリクエストを非同期で行えることを可能にしてくれます。

sendBeaconを使わなくてもunload時などのタイミングでAPIリクエストを投げることはできます。

非同期での処理が行なえないだけなのでAPIリクエストを同期的に行えばできなくはないのですが、
同期的に処理を行うことでページ読み込み時に読み込み処理を一時的にブロックしてしまうため、
ここはトレードオフです。

また、私がsendBeaconを検証したときにいくつか感じた疑問や発生した課題について共有します。

送信したいリクエストのメソッドがPOST以外の場合

例えばRailsのmultipart/form-data送信仕様でformdataのパラメータに_method=PUTを持たせることでフォーム送信でPUTリクエストを投げることが可能ですがこちら試してもうまくいきませんでした。。

Json送れるのか問題

Blob使えば送れるみたいです。

  const handleUnload = () => {

    // JSONで送信する場合
     const body = new Blob(
       [
         JSON.stringify({
           key: value,
         }),
       ],
       { type: 'application/json' }
     );

    navigator.sendBeacon(`${apiUrl}`, body);
  };

addEventListener('unload', handleUnload, false);

CSRFトークンなどの情報を渡せるか問題

JSONやFormDataでキーを追加すれば可能

sendBeaconでリクエストを投げることは確認できたけどpending状態となっているけど大丈夫か問題

想定の挙動みたいです。

sendBeaconはページのアンロード時に非同期処理の実行を保証してくれるが、その後ページが破棄されレスポンスを受け取ることができないのでpending状態となっているそうだ。

stackoverflow.com

devtoolからsendBeaconでのAPIリクエストを確認するには?

Networkタブの「xhr」で確認したところ表示されていなかったが「その他」に切り替えたら表示された。

そのため、あれ?表示されてないなと思ったら「その他」に切り替えて確認してみてください。