fix: 🐛 also wait for loaders added while loading

This commit is contained in:
Christian Kaisermann 2020-05-31 12:50:17 -03:00
parent 1148b72c7d
commit e560514b1d
2 changed files with 61 additions and 25 deletions

View File

@ -7,24 +7,28 @@ import {
import { getRelatedLocalesOf } from '../stores/locale' import { getRelatedLocalesOf } from '../stores/locale'
type Queue = Set<MessagesLoader> type Queue = Set<MessagesLoader>
const loaderQueue: Record<string, Queue> = {} const queue: Record<string, Queue> = {}
export function resetQueues() { export function resetQueues() {
Object.keys(loaderQueue).forEach(key => { Object.keys(queue).forEach(key => {
delete loaderQueue[key] delete queue[key]
}) })
} }
function createLocaleQueue(locale: string) { function createLocaleQueue(locale: string) {
loaderQueue[locale] = new Set() queue[locale] = new Set()
} }
function removeLocaleFromQueue(locale: string) { function removeLoaderFromQueue(locale: string, loader: MessagesLoader) {
delete loaderQueue[locale] queue[locale].delete(loader)
if (queue[locale].size === 0) {
delete queue[locale]
}
} }
function getLocaleQueue(locale: string) { function getLocaleQueue(locale: string) {
return loaderQueue[locale] return queue[locale]
} }
function getLocalesQueues(locale: string) { function getLocalesQueues(locale: string) {
@ -40,33 +44,46 @@ function getLocalesQueues(locale: string) {
export function hasLocaleQueue(locale: string) { export function hasLocaleQueue(locale: string) {
return getRelatedLocalesOf(locale) return getRelatedLocalesOf(locale)
.reverse() .reverse()
.some(getLocaleQueue) .some(locale => getLocaleQueue(locale)?.size)
} }
const activeLocaleFlushes: { [key: string]: Promise<void> } = {} function loadLocaleQueue(locale: string, queue: MessagesLoader[]) {
export function flush(locale: string) { const allLoadersPromise = Promise.all(
if (!hasLocaleQueue(locale)) return queue.map(loader => {
if (locale in activeLocaleFlushes) return activeLocaleFlushes[locale] // todo: maybe don't just remove, but add to a `loading` set?
removeLoaderFromQueue(locale, loader)
return loader().then(partial => partial.default || partial)
})
)
return allLoadersPromise.then(partials => addMessages(locale, ...partials))
}
const activeFlushes: { [key: string]: Promise<void> } = {}
export function flush(locale: string): Promise<void> {
if (!hasLocaleQueue(locale)) {
if (locale in activeFlushes) {
return activeFlushes[locale]
}
return
}
// get queue of XX-YY and XX locales // get queue of XX-YY and XX locales
const queues = getLocalesQueues(locale) const queues = getLocalesQueues(locale)
// istanbul ignore if
if (queues.length === 0) return
// TODO what happens if some loader fails // todo: what happens if some loader fails?
activeLocaleFlushes[locale] = Promise.all( activeFlushes[locale] = Promise.all(
queues.map(([locale, queue]) => { queues.map(([locale, queue]) => loadLocaleQueue(locale, queue))
return Promise.all(queue.map(loader => loader())).then(partials => {
removeLocaleFromQueue(locale)
partials = partials.map(partial => partial.default || partial)
addMessages(locale, ...partials)
})
})
).then(() => { ).then(() => {
delete activeLocaleFlushes[locale] if (hasLocaleQueue(locale)) {
return flush(locale)
}
delete activeFlushes[locale]
}) })
return activeLocaleFlushes[locale] return activeFlushes[locale]
} }
export function registerLocaleLoader(locale: string, loader: MessagesLoader) { export function registerLocaleLoader(locale: string, loader: MessagesLoader) {

View File

@ -50,3 +50,22 @@ test('consecutive flushes return the same promise', async () => {
expect(flushB).toStrictEqual(flushA) expect(flushB).toStrictEqual(flushA)
expect(flushC).toStrictEqual(flushA) expect(flushC).toStrictEqual(flushA)
}) })
test('waits for loaders added while already flushing', async () => {
registerLocaleLoader(
'en',
() => new Promise(res => setTimeout(() => res({ foo: 'foo' }), 300))
)
const flushPromise = flush('en')
registerLocaleLoader(
'en',
() => new Promise(res => setTimeout(() => res({ bar: 'bar' })))
)
await flushPromise
expect(getMessageFromDictionary('en', 'foo')).toBe('foo')
expect(getMessageFromDictionary('en', 'bar')).toBe('bar')
})