質問するログイン新規登録

意見交換

5回答

175閲覧

Service Workerのポーリングがタブ非アクティブ時に止まる問題

ngmg

総合スコア77

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

0クリップ

投稿2026/05/14 03:43

編集2026/05/14 03:56

0

0

Service Worker内でsetIntervalを使ってポーリング処理を実装しています。タブがアクティブなときは正常に動作しますが、非アクティブ(別タブや別ウィンドウに切り替え)にすると止まってしまいます。

奇妙な挙動

DevToolsを別ウィンドウで開いて監視していると、タブが非アクティブでもポーリングが継続します。DevToolsを閉じると止まります。

コード

sw.js

javascript

1var timer = null; 2var baseId = null; 3var apiUrl = null; 4var interval = 60000; 5 6self.addEventListener('message', function(e) { 7 var data = e.data; 8 if (data.type === 'start') { 9 apiUrl = data.apiUrl; 10 interval = data.interval; 11 if (data.baseId !== null && data.baseId !== undefined) baseId = data.baseId; 12 startPolling(); 13 } 14 if (data.type === 'stop') { 15 stopPolling(); 16 } 17}); 18 19function startPolling() { 20 stopPolling(); 21 poll(); 22 timer = setInterval(poll, interval); 23} 24 25function stopPolling() { 26 if (timer) { clearInterval(timer); timer = null; } 27} 28 29function poll() { 30 fetch(apiUrl) 31 .then(function(r) { return r.json(); }) 32 .then(function(data) { 33 if (baseId === null) { baseId = data.id; return; } 34 if (baseId !== null && data.id > baseId) { 35 stopPolling(); 36 self.clients.matchAll({ includeUncontrolled: true, type: 'window' }).then(function(clients) { 37 clients.forEach(function(client) { 38 client.postMessage({ type: 'new_posts' }); 39 }); 40 }); 41 } 42 }) 43 .catch(function(e) {}); 44}

ページ側(layout.html)

javascript

1navigator.serviceWorker.register('/sw.js').then(function(reg) { 2 // 登録 3}); 4 5navigator.serviceWorker.ready.then(function(reg) { 6 sw = reg.active; 7 if (sw) sw.postMessage({ type: 'start', apiUrl: apiUrl, interval: interval, baseId: baseId }); 8}); 9 10navigator.serviceWorker.addEventListener('message', function(e) { 11 if (e.data.type === 'new_posts') showNotification(); 12});

環境

  • Chrome(最新)
  • HTTPS環境
  • Service WorkerのスコープはサイトルートURL(/sw.jsをルートに配置)

質問

  1. DevToolsを開いているときだけ動く原因はなんでしょうか?
  2. Service Worker内のsetIntervalをタブ非アクティブ時も動かし続ける方法はありますか?
  3. Push API以外で実現する方法はありますか?

最終的にやりたいのはYahoo!メールのように、タブが非アクティブでもタブのアイコンにバッジが付く挙動です。
通知許可はしていない状態です。現在はService Worker内でsetIntervalを使ってポーリングし、新着を検知したらcanvasでファビコンに色丸を追加する実装をしています。タブがアクティブなときは動作しますが、非アクティブだと止まってしまいます。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

回答5

#1

yambejp

総合スコア118447

投稿2026/05/14 07:52

「Push API以外で実現」というのはサーバー側のコントロールする権限がないということでしょうか?本来ならCometなどサーバー側の付加価値で処理するのでしょうけど、最低限のプログラムが使えるならピギーバックやSSEでいけそうな案件のような気がします。逆にもしクライアント側がけでなんとかしたいとなるとひたすら取得しつづけるかないでしょうね。
ブラウザの非アクティブ時は負荷を下げるためにインターバル処理などは動作が保証されないのでしょう。ただjavascriptのポーングなんてサーバーから見たらただの攻撃ですから、自前の管理サーバーでなければ使用条件をよく確認したほうがいいですね。非アクティブを回避するなら自前でクローラーを用意して、結果を受け取っておいてアクティブになったときに取得するとかですかねぇ・・・

#2

ngmg

総合スコア77

投稿2026/05/14 08:24

ありがとうございます。サーバーは自前のVPSです。
最終的にやりたいことはサイトに新着投稿があったときにタブのファビコンにバッジを付けることです。Push APIを使わない理由はPVがまだ多くないので購読情報の管理など大掛かりな実装を避けたかったからです。
現状はポーリングでタブがアクティブなときはバッジが付いています。非アクティブでも動かしたいのですが、SSEで実現できますか?それともやはりPush API以外では難しいでしょうか?

#3

Lhankor_Mhy

総合スコア37760

投稿2026/05/14 09:41

 些末なことで恐縮なのですが、あまり重い処理ではなさそうなのでワーカーで処理をする必要がなさそうに思いました。ファビコンの変更などはワーカーから処理を戻す必要があると思うので……、ここに書かれていない重い処理がある感じですか?

 また、専用ワーカーではなくてサービスワーカーで処理をしている理由などはありますか?

#4

yambejp

総合スコア118447

投稿2026/05/15 03:23

編集2026/05/15 08:35

以下SSEの機能を確認用にサンプルあげておきます
課題はいくつかあって安定度は完全ではないですが複数ブラウザやタブをひらいた状態でどの画面から送ったデータも他のブラウザで確認できます。
ポイントはSSEがメッセージを発生させるためのトリガーをどうするか次第ですね

SSE簡易チャット

※微調整しました

  • chat.html

html

1<script> 2window.addEventListener('DOMContentLoaded', ()=>{ 3 const box=document.querySelector('#box'); 4 const message=document.querySelector('#message'); 5 const send=document.querySelector('#send'); 6 const evtSource = new EventSource('api.php?q=get',); 7 evtSource.onmessage = function (e){ 8 const json=JSON.parse(e.data); 9 box.appendChild(Object.assign(document.createElement('div'),{textContent:json.message})); 10 box.scrollTop = box.scrollHeight; 11 } 12 evtSource.addEventListener('keep-alive',e=>{ 13 console.log(JSON.parse(e.data).message); 14 }); 15 send.addEventListener('click',e=>{ 16 const usp=new URLSearchParams(); 17 if(message.value){ 18 usp.set('q','send'); 19 usp.set('m',message.value); 20 fetch('api.php?'+usp); 21 message.value=''; 22 } 23 }); 24}); 25</script> 26<style> 27#box{ 28width:500px; 29height:300px; 30background-Color:lightgray; 31overflow-Y:scroll; 32} 33</style> 34<div id="box"> 35</div> 36<input id="message"><input type="button" id="send" value="send">
  • api.php

php

1<?PHP 2$q=filter_input(INPUT_GET,"q"); 3$m=filter_input(INPUT_GET,"m"); 4$filename="chat.txt"; 5if($q=="send"){ 6 $file = new SplFileObject($filename, "a+"); 7 $file->fwrite('['.microtime(true).']'.$m.PHP_EOL); 8 exit; 9} 10if($q=="get"){ 11 header("Cache-Control: no-store"); 12 header("Content-Type: text/event-stream"); 13 date_default_timezone_set("Asia/Tokyo"); 14 $mt=microtime(true); 15 $prev_mt=$mt; 16 $file = new SplFileObject($filename, "r"); 17 while (true) { 18 foreach ($file as $line) { 19 if(preg_match("/^\[([\d\.]+)\](.+?)$/",$line,$match)){ 20 if($match[1]-$mt > 0){ 21 print "event: message".PHP_EOL; 22 print "data:{\"message\":".json_encode($match[2])."}".PHP_EOL.PHP_EOL; 23 print str_repeat(' ',4096).PHP_EOL; 24 } 25 } 26 } 27 flush(); 28 if($mt-$prev_mt>30){ 29 print "event: keep-alive".PHP_EOL; 30 print "data:{\"message\":\"".date("Y-m-d H:i:s")."\"}".PHP_EOL.PHP_EOL; 31 print str_repeat(' ',4096).PHP_EOL.PHP_EOL; 32 $prev_mt=$mt; 33 } 34 if ( connection_aborted() ) break; 35 $mt=microtime(true); 36 usleep(100000); 37 } 38} 39`` 40

#5

ngmg

総合スコア77

投稿2026/05/15 04:24

こちらを参考に勉強させていただきます。
ご丁寧にありがとうございました!

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

この意見交換はまだ受付中です。

会員登録して回答してみよう

アカウントをお持ちの方は

関連した質問