async/await
を使ってきちんと非同期処理の実行順序を制御するためには、async
関数内で使用するsetTimeout()
のようなコールバック関数をPromise化する必要があります。
で、普通にPromise化する分にはいいんですが、clearTimeout()
を呼んだときのように、外から実行を中止できるようなPromise関数を作らなければいけなかったのでメモ。
ほぼ以下のパクリですが、いくつか改変しているので、メモっておきます
最終的なコードは以下です
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()
を呼び出します。
const a = asyncSetTimeout(1000,asyncFunc)
await a.exec() // ここで設定した時間分処理を待ったあとasyncFuncを実行する
もしくはもっと単純に以下のように使うこともできます
const a = asyncSetTimeout(1000)
await a.exec() // ここで設定した時間分処理を待ったあとasyncFuncを実行する
await asyncFunc()
中断したいときはcancel()
を呼び出します。
let cancel
(async ()=>{
const a = asyncSetTimeout(1000,asyncFunc)
cancel = a.cancel
await a.exec() // ここで設定した時間分処理を待ったあとasyncFuncを実行する
dosomething()
})()
cancel() // cancel()を呼び出すと、setTimeout処理を中断し、asyncFuncを実行せずに次の処理(doSomething)を呼び出してくれる
また、以下のように使うこともできますが、この場合は、cancel()
を実行してもasyncFunc()
の実行は中止されません。あくまでsetTimeout()
の処理が中止されるだけです。
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してくれません。
今回に限らず、引数に関数を取る関数を作るときは、非同期関数がやってくることを想定して作りましょう
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
が永遠に解決しない関数になってしまいます。
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()
)が走らなくなってしまいます。気をつけましょう。
サンプルコード