LoginSignup
18
13

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-05-18

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

で、普通にPromise化する分にはいいんですが、clearTimeout()を呼んだときのように、外から実行を中止できるような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())が走らなくなってしまいます。気をつけましょう。

サンプルコード

18
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
13