キャンセル可能でPromiseなsetTimeout()を作る

`async/await`を使ってきちんと非同期処理の実行順序を制御するためには、`async`関数内で使用する`setTimeout()`のようなコールバック関数をPromise化する必要があります。

で、普通にPromise化する分にはいいんですが、`clearTimeout()`を呼んだときのように、外から実行を中止できるようなPromise関数を作らなければいけなかったのでメモ。

ほぼ以下のパクリですが、いくつか改変しているので、メモっておきます

https://stackoverflow.com/questions/25345701/how-to-cancel-timeout-inside-of-javascript-promise

最終的なコードは以下です

``` asyncSetTimeout.js

function asyncSetTimeout(msec, func = () => {}){
    let timeoutId
    let r
    const exec = () => new Promise((res) => {
            r = res
            timeoutId = setTimeout(async () => {
                timeoutId = null
                await func()
                res()
            },msec)
        })
    return {
        exec,
        cancel: () => {
            if (timeoutId) {
              clearTimeout(timeoutId)
              timeoutId = null
              r()
            }
        }
    }
}
```

使い方は以下のような感じです。処理を実行するときは`exec()`を呼び出します。


``` 実行するとき.js
   
    const a = asyncSetTimeout(1000,asyncFunc)
    await a.exec() // ここで設定した時間分処理を待ったあとasyncFuncを実行する
```

もしくはもっと単純に以下のように使うこともできます

``` 実行するとき.js
    const a = asyncSetTimeout(1000)
    await a.exec() // ここで設定した時間分処理を待ったあとasyncFuncを実行する
    await asyncFunc()
```

中断したいときは`cancel()`を呼び出します。

```処理を中止するとき.js
let cancel

(async ()=>{
    const a = asyncSetTimeout(1000,asyncFunc)
    cancel = a.cancel
    await a.exec() // ここで設定した時間分処理を待ったあとasyncFuncを実行する
    dosomething()
})()

cancel() // cancel()を呼び出すと、setTimeout処理を中断し、asyncFuncを実行せずに次の処理(doSomething)を呼び出してくれる
```

また、以下のように使うこともできますが、この場合は、`cancel()`を実行しても`asyncFunc()`の実行は中止されません。あくまで`setTimeout()`の処理が中止されるだけです。

```処理を中止するとき.js
let cancel

(async ()=>{
    const a = asyncSetTimeout(1000)
    cancel = a.cancel
    await a.exec() // ここで設定した時間分処理を待ったあとasyncFuncを実行する
    await asyncFunc()
    dosomething()
})()

cancel() // cancel()を呼び出すと、setTimeout処理を中断し、次の処理(asyncFunc)を呼び出してくれる
```

以上。

**以下はこの`asyncSetTiomeout()`を作るときの注意点です。使う分には関係ないので読まなくて良いです**

## 引数に`async`関数がやってくることを想定する


以下のようなコードを書くと、引数に指定した`asyncFunc`の中でawaitしてくれません。
今回に限らず、引数に関数を取る関数を作るときは、非同期関数がやってくることを想定して作りましょう

``` 間違ったコード.js
function asyncSetTimeout(msec, func = () => {}){
    let timeoutId
    let r
    const exec = () => new Promise((res) => {
            r = res
            timeoutId = setTimeout(() => {
                func() // awaitをつけていない
                res()
            },msec)
        })
    return {
        exec,
        cancel: () => {
            clearTimeout(timeoutId)
            r()
        }
    }
}
```

## `clearTimeout()`を呼び出すと、Promiseが`resolve()`しなくなる

例えば以下のようなコードは、`cancel()`を呼び出したときに、`Promise`が永遠に解決しない関数になってしまいます。

``` 間違ったコード2.js
function asyncSetTimeout(msec, func = () => {}){
    let timeoutId
    let r
    const exec = () => new Promise((res) => {
            r = res
            timeoutId = setTimeout(async () => {
                await func()
                res()
            },msec)
        })
    return {
        exec,
        cancel: () => {
            clearTimeout(timeoutId) // clearTimeout後にexec()のresolve()を呼び出していない
        }
    }
}
```

こうなってしまうと、`cancel()`したときに、永遠に次のコード(`dosomething()`)が走らなくなってしまいます。気をつけましょう。

サンプルコード

https://gist.github.com/kuwabataK/4bcd81f0c14af8ebbdf13249c7eb6a4e