JavaScriptで15分毎にデスクトップ通知するアラームを作る（通知APIとクロージャ）

## やりたいこと

個人での長時間のPC作業中に、振り返ったり頭を切り替えたりできるようにするため、15分くらいの間隔でお知らせしてくれるアラームがほしい。

## 方法の検討

* スマホのアプリだとPC作業に気づかなかったり、逆に集中が切れてしまったりするので、PCでこっそり動くのがいい
* でも作業用PCに余計なアプリは入れたくない

自分でJavaScriptで作って、ブラウザで開いておく方法を試してみる。

## 通知APIを動かす

JavaScriptを使うにしても、余計なライブラリはなるべく使わずに済ませたい。
幸い標準で通知APIなるものが用意されてるので、これを使う。
まずは単に通知を表示するスクリプトを作ってみる。

```javascript:notification.js
window.onload = function() {
   Notification.requestPermission();
   const notification = new Notification("Check!");
};
```

↑を読み込む適当なHTMLを作って、ブラウザで開く。

```html:alarm15.html
<html>
<head>
   <script src="notification.js"></script>
</head>
<body>
</body>
</html>

```

HTMLを表示すると、よくある感じの通知の許可要求ダイアログが表示される。
（このへんWebに公開する場合はいろいろ考えなきゃいかんけど、ローカルなのであまり気にせず）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27654/e595f58a-aa1c-4b10-0347-f608136ab9f3.png)
許可すると、画面右上によくある感じの通知が表示される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27654/2785e8f0-6a7a-0225-f38c-33ba312e269e.png)
これを15分ごとに表示させるようにしたい。

## 実装その1 シンプルに

まずは素直に実装してみる。

```javascript:alarm15-simple.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};
 
let previousMinutes;

const checkTime = function() {
    const currentTime = new Date();
    const minutes = currentTime.getMinutes();
    if (previousMinutes !== minutes && minutes % 15 === 0) {
        previousMinutes = minutes;
        const notification = new Notification("Check!");
    }
};
```

`setInterval(checkTime, 1000)` と書くことで、1秒間隔で関数 `checkTime` を実行する。
関数 `checkTime` では現在時刻をチェックして、0/15/30/45分だったら一度だけデスクトップ通知をする。
（通知の間隔を変えたければ `minutes % 15` の数値を変えればよい。最初は1分間隔で試すとよい）

これでやりたいことは実現できたけど…
このコードだと、直前の「分」を保持している変数 `previousMinutes` をグローバルスコープに置いてしまっている。
これを安全なスコープに閉じ込めてあげたい。

## 実装その2 クロージャ

このようにしてスコープを閉じられた。見た目の動作は同じ。

```javascript:alarm15-closure.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};
 
const checkTime = function() {
   let previousMinutes;
   return function() {
       const currentTime = new Date();
       const minutes = currentTime.getMinutes();
       if (previousMinutes !== minutes && minutes % 15 === 0) {
           previousMinutes = minutes;
           const notification = new Notification("Check!");
       }
   }  
}();
```
いい感じのクロージャのサンプルになったので、ざっくり説明。

定数 `checkTime` には、無名関数を即時実行した結果が代入される。
無名関数を即時実行して返ってくる結果は `return function() ...` と記述のある通り、これまた無名関数。
この無名関数のコードは実装その1の `checkTime` 関数とまったく同じ。
結果として、実装その1とその２はまったく同じ関数 `checkTime` を実行するように見えるけど…。

実装その1とその２で、関数 `checkTime` の違いは、関数のスコープ。
実装その1は、最も外側に関数が定義されているので、関数 `checkTime` のスコープはグローバルスコープ。
実装その2は、外側の無名関数内に関数が定義されているので、関数 `checkTime` のスコープは外側の無名関数が作るローカルスコープ。
（このように、実行時でなく定義時に決まるスコープをレキシカルスコープと言う）

で、実装その2は同じローカルスコープに変数 `previousMinutes` が定義されている。
同じスコープなので関数 `checkTime` からアクセスできるけど、他のスコープにある関数からはアクセスできない。
これで、変数 `previousMinutes` は必要な関数からのみアクセスされる、安全なスコープに閉じ込めることができた。

## おまけ設定

通知が味気ないので、ちょっとだけオプションを設定してみる。

```javascript:alarm15-closure.js
window.onload = function() {
   Notification.requestPermission();
   setInterval(checkTime, 1000);   
};
 
const checkTime = function() {
   let previousMinutes;
   const options = {
       body: "調子はどうだい？",
       icon: "img/youkai_kappa.png"
   };
   return function() {
       const currentTime = new Date();
       const minutes = currentTime.getMinutes();
       if (previousMinutes !== minutes && minutes % 15 === 0) {
           previousMinutes = minutes;
           const notification = new Notification("Check!", options);
       }
   }  
}();
```
こんなかんじになる。
（画像はお好みで）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27654/87c3e245-5998-b50f-b589-a2014fe865e4.png)

## 参考資料

[Notifications API を使用する - WebAPI | MDN](https://developer.mozilla.org/ja/docs/WebAPI/Using_Web_Notifications)
[WindowOrWorkerGlobalScope.setInterval() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Window/setInterval)
[開眼！ JavaScript](https://www.amazon.co.jp/dp/487311621X) 第7章「スコープとクロージャ」
[JavaScript の原理：クロージャの真実](https://qiita.com/takuya0301/items/278f1d9507c28a251735)

## おわりに

この記事は [Mikatus Advent Calendar 2019](https://qiita.com/advent-calendar/2019/mikatus) 7日目として公開しました。
明日は我らがリーダー @takuya0301 さんです！
参考資料の「クロージャの真実」もぜひご覧ください（本記事の説明はざっくりなので）。