flexlock-cb
is a very small, memory-concious, flexible locking library without
dependencies but with typescript
definitions (see in the bottom). Optimized even further than
flexlock
for the use with callbacks instead of promises.
npm i flexlock-cb --save
It is similar to other in-memory locking library like mutexify
,
mutex-js
, await-lock
, and many more,
but with more flexibility in how to use it.
This makes it sturdier and more practical in many cases.
const { createLockCb } = require('flexlock-cb')
const lock = createLockCb()
lock(unlock => {
// done before the next block
unlock()
})
lock(unlock => {
// will wait for the
unlock()
})
function callback (err, data) {
// err === null
// data === 'important'
}
lock(
unlock => unlock(null, 'important'),
callback
)
const promise = lock(unlock => {
unlock(null, 'important')
}) // Without passing in a callback, promises will be created
promise
.catch(err => {})
.then(data => {})
// This way you can support both callback and promise based APIs
function neverUnlock (unlock) { /* Due to a bug it never unlocks */ }
function neverCalled () {}
lock(neverUnlock)
lock(neverCalled, 500, err => {
err.code === 'ETIMEOUT'
})
function onEveryRelease () {}
function onNextRelease () {}
const lock = createLockCb(onEveryRelease) // Called everytime the lock is released
lock.released(onNextRelease) // Called next time the lock is released
await lock.released() // Promise API available as well
function onSuccess (data) {}
function onError (err) {}
lock(unlock => unlock(), onSucess, onError)
const lock = createLockCb()
const result = await lock.sync(() => {
// no unlock function (automatically unlocked after method is done)
return 123
})
result === 123 // the result is passed to the callback
In case you need it, a reference to the lock is also passed in:
const result = await lock.sync(lock => { /* ... */ })
Its also possible to wrap a method into a sync lock:
const fn = lock.syncWrap((foo, bar) => {
/**
- No unlock function.
- Arguments are passed-through.
- Executed will be asynchronously.
- Return value will be ignored.
*/
foo === 'hello'
bar === 'world'
})
fn('hello', 'world')
Be aware that any errors that might occur will by-default result in uncaught exceptions!
You can handle those errors by passing an error handler when creating the lock:
const lock = createLockCb(null, err => {
// Here you can handle any error, for example: emit('error', err)
})
const fn = lock.syncWrap(() => {
throw new Error('error')
})
... or by adding a error handler directly when wrapping:
const fn2 = lock.syncWrap(() => {
throw new Error('error')
}, err => {
// Handle an error thrown in the sync-wrap
})
When closing down an application you may want to also close all operations and prevent future operations:
import { createLockCb } from 'flexlock-cb'
const lock = createLockCb()
lock.destroy(new Error('lock destroyed') /* optional */)
try {
lock(cb => {
// will not be executed
})
} catch (err) {
// err ... will be the error passed-in to .destroy()
}
Figuring out the proper typing was quite tricky for flexlock-cb.
To make things easier for users, it exports a Callbacks
type that
can be used to reduce
import { Callbacks, createLockCb } from 'flexlock-cb'
const lock = createLockCb()
//
// Overloading for the two use cases (return type void or Promise)
// If you would like to improve this, vote for
// https://github.com/Microsoft/TypeScript/issues/29182
//
function fn (foo: string, bar: number, ...cb: Callbacks<string>): void
function fn (foo: string, bar: number): Promise<string>
function fn (foo: string, bar: number, ...cb) {
return lock(() => `${foo} ${bar}`, 0, ...cb)
}
// Separate definitions with the support for timeouts
import { CallbacksWithTimeout } from 'flexlock-cb'
function fnTime (foo: string, bar: number, ...cb: CallbacksWithTimeout<string>): void
function fnTime (foo: string, bar: number, timeout?: number): Promise<string>
function fnTime (foo: string, bar: number, ...cb) {
return lock(() => `${foo} ${bar}`, ...cb)
}