LoginSignup
6
2

More than 3 years have passed since last update.

タイムアウトした時にちゃんとロックを解除してくれる async-lockを作る

Last updated at Posted at 2019-08-16

Promiseの排他制御をしてくれるライブラリにasync-lockあります。

で、このライブラリには、ロックしているPromiseの実行が一定時間以上立った場合に Error を投げてくれるTimeoutというオプションがあるのですが、こいつはTimeOutになってもエラーを投げるだけでロックを解除してはくれません。

なので、一定時間経過後に問答無用でロックを解除したい場合は、自分でロックを解除するコードを書かないといけないです。

まあ、処理が長過ぎるときのエラーハンドリングは、内部で実行されているPromise関数が頑張れと言うことなのかもしれませんが、、それってTimeoutの意味なくない?ってなったので自作しました。(といっても本家のライブラリをラップしただけですが・・・)

TypeScriptで書いちゃってますが、型定義部分削ればJavaScriptでも使えると思います。

コード

実行にはasync-lockが必要なので事前にインストールしてください

$ npm install async-lock
$ npm install --save-dev  @types/async-lock

作成したラッパークラスは以下です

import AsyncLock from 'async-lock'

export class PromiseLock {

    asyncLock: AsyncLock
    constructor(option?: AsyncLockOptions) {
        this.asyncLock = new AsyncLock(option)
    }

    acquire(func: Function, options: AquireOptions = {}) {

        let { resolveTimeout, key } = options
        if (!key) {
            key = '__DEFAULT_KEY__'
        }

        return this.asyncLock.acquire(key, () => {
            return new Promise(async resolve => {
                let isResolved = false
                if (resolveTimeout) {
                    setTimeout(() => {
                        if (!isResolved) {
                            console.error(`${resolveTimeout} ms passed. over resolveTimeout. unlock promiseLock`)
                            resolve()
                        }
                    }, resolveTimeout)
                }
                await func()
                resolve()
                isResolved = true
            })
        })
    }

    isBusy(key = '__DEFAULT_KEY__') {
        return this.asyncLock.isBusy(key)
    }
}

interface AsyncLockOptions {
    timeout?: number;
    maxPending?: number;
    domainReentrant?: boolean;
    Promise?: any;
}

interface AquireOptions {
    resolveTimeout?: number
    key?: string | string[]
}

使い方

使い方は以下のような感じです。だいたい本家と一緒ですが、不要ならkeyとか指定しなくても良くなってます。

    const promiseLock = new PromiseLock()

    const asyncHandler = () => { 
        // promiseLock.acquireの第一引数に指定した関数の実行が終わるまでロックする
        promiseLock.acquire(async () => {
            console.log('start')
            await Promise処理()
            console.log('finish')
        })
    }

第2引数にresolveTimeoutを指定することで、指定した時間経過後に強制的にロックを解除することができます。
本家と違ってErrorもスローしません。(console.error()は吐きますが・・・)

        promiseLock.acquire(async () => {
            console.log('start')
            await Promise処理()
            console.log('finish')
        }, { resolveTimeout: 2000 }) // 2秒以上処理が終わらない場合は強制的にロックを解除する

一応キーも指定することができます。

        promiseLock.acquire(async () => {
            console.log('start')
            await Promise処理()
            console.log('finish')
        },{ key: 'original_key' })

isBusy()も使えます。これは、acquireの中の関数が実行中であればtrueを返す関数です。特にresolveTimeoutと組み合わせると、「処理が終わるまでは次の呼び出しを実行しないようにし、処理に時間がかかりすぎる場合はロックを解除する」みたいな動きができるので、ゲームのスクロール処理とか、「時間経過で処理を行いたいけど、遅延して実行するぐらいなら処理落ちしたほうが良い」みたいな時に結構使えます。

    const handler = () => {
        // acquireの処理中は次の処理を受け付けない
        if (promiseLock.isBusy()) {
            console.log('handler is busy. skip exec function')
            return
        }

        promiseLock.acquire(async () => {
            console.log('start')
            await Promise処理()
            console.log('finish')
        }, { resolveTimeout: 2000 }) // 2秒以上処理が遅延する場合には、強制的にロックを解除し、次の処理を実行する
    }

本家のoptionを使いたい場合は、newする時に指定することもできます。(ただここで指定できるtimeoutは本家のエラーを吐くだけのtimeoutなので気をつけてください)

    const promiseLock = new PromiseLock({ maxPenging: 100 })

以上

6
2
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
6
2