diff --git a/src/helpers/db/index.js b/src/helpers/db/index.js index 2f2ed528..5285bf0c 100644 --- a/src/helpers/db/index.js +++ b/src/helpers/db/index.js @@ -9,7 +9,7 @@ import get from 'lodash/get' import set from 'lodash/set' import logger from 'logger' -import { promisify } from 'helpers/promise' +import { promisify, debounce } from 'helpers/promise' import { NoDBPathGiven, DBWrongPassword } from 'config/errors' @@ -24,13 +24,14 @@ const writeFileAtomic = promisify(writeFileAtomicModule) const ALGORITHM = 'aes-256-cbc' -let queue = Promise.resolve() - let DBPath = null let memoryNamespaces = {} let encryptionKeys = {} let transforms = {} +const DEBOUNCE_MS = process.env.NODE_ENV === 'test' ? 1 : 500 +const save = debounce(saveToDisk, DEBOUNCE_MS) + /** * Reset memory state, db path, encryption keys, transforms.. */ @@ -232,11 +233,6 @@ async function saveToDisk(ns: string) { await writeFileAtomic(path.resolve(DBPath, `${ns}.json`), fileContent) } -function save(ns: string) { - queue = queue.then(() => saveToDisk(ns)) - return queue -} - async function cleanCache() { logger.onDB('clean cache') await setKey('app', 'countervalues', null) diff --git a/src/helpers/promise.js b/src/helpers/promise.js index ad494a16..529fdb23 100644 --- a/src/helpers/promise.js +++ b/src/helpers/promise.js @@ -72,3 +72,29 @@ export const promisify = (fn: any) => (...args: any) => return resolve(res) }), ) + +export const debounce = (fn: any => any, ms: number) => { + let timeout + let resolveRefs = [] + let rejectRefs = [] + return (...args: any) => { + const promise = new Promise((resolve, reject) => { + resolveRefs.push(resolve) + rejectRefs.push(reject) + }) + if (timeout) { + clearTimeout(timeout) + } + timeout = setTimeout(async () => { + try { + const res = await fn(...args) + resolveRefs.forEach(r => r(res)) + } catch (err) { + rejectRefs.forEach(r => r(err)) + } + resolveRefs = [] + rejectRefs = [] + }, ms) + return promise + } +} diff --git a/src/helpers/promise.spec.js b/src/helpers/promise.spec.js new file mode 100644 index 00000000..b42657f4 --- /dev/null +++ b/src/helpers/promise.spec.js @@ -0,0 +1,57 @@ +import { debounce, delay } from 'helpers/promise' + +describe('promise helper', () => { + describe('debounce', () => { + test('returns a promise', () => { + const noop = () => {} + const debouncedNoop = debounce(noop, 0) + const res = debouncedNoop() + expect(res).toBeInstanceOf(Promise) + }) + + test('debounce the call', async () => { + let num = 0 + const increment = () => (num += 1) + const debouncedIncrement = debounce(increment, 100) + + debouncedIncrement() // should be cancelled + await delay(10) + debouncedIncrement() // should increment + await delay(100) + + expect(num).toBe(1) + }) + + test('returns the correct promise, for all calls', async () => { + let num = 0 + const increment = () => (num += 1) + const debouncedIncrement = debounce(increment, 100) + + const promise1 = debouncedIncrement() // should be cancelled + debouncedIncrement() // should be cancelled + debouncedIncrement() // should be cancelled + debouncedIncrement() // should be cancelled + debouncedIncrement() // should be cancelled + debouncedIncrement() // should increment to 1 + await promise1 + + expect(num).toBe(1) + }) + + test('forwards error', async () => { + const failingIncrement = () => { + throw new Error('nope') + } + const debouncedFailingIncrement = debounce(failingIncrement, 100) + + let err + try { + await debouncedFailingIncrement() + } catch (e) { + err = e + } + expect(err).toBeDefined() + expect(err.message).toBe('nope') + }) + }) +})