Skip to content

Commit

Permalink
fix: 🐛 also wait for loaders added while loading
Browse files Browse the repository at this point in the history
  • Loading branch information
kaisermann committed May 31, 2020
1 parent 1148b72 commit e560514
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 26 deletions.
69 changes: 43 additions & 26 deletions src/runtime/includes/loaderQueue.ts
Expand Up @@ -7,24 +7,28 @@ import {
import { getRelatedLocalesOf } from '../stores/locale'

type Queue = Set<MessagesLoader>
const loaderQueue: Record<string, Queue> = {}
const queue: Record<string, Queue> = {}

export function resetQueues() {
Object.keys(loaderQueue).forEach(key => {
delete loaderQueue[key]
Object.keys(queue).forEach(key => {
delete queue[key]
})
}

function createLocaleQueue(locale: string) {
loaderQueue[locale] = new Set()
queue[locale] = new Set()
}

function removeLocaleFromQueue(locale: string) {
delete loaderQueue[locale]
function removeLoaderFromQueue(locale: string, loader: MessagesLoader) {
queue[locale].delete(loader)

if (queue[locale].size === 0) {
delete queue[locale]
}
}

function getLocaleQueue(locale: string) {
return loaderQueue[locale]
return queue[locale]
}

function getLocalesQueues(locale: string) {
Expand All @@ -40,33 +44,46 @@ function getLocalesQueues(locale: string) {
export function hasLocaleQueue(locale: string) {
return getRelatedLocalesOf(locale)
.reverse()
.some(getLocaleQueue)
.some(locale => getLocaleQueue(locale)?.size)
}

function loadLocaleQueue(locale: string, queue: MessagesLoader[]) {
const allLoadersPromise = Promise.all(
queue.map(loader => {
// 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 activeLocaleFlushes: { [key: string]: Promise<void> } = {}
export function flush(locale: string) {
if (!hasLocaleQueue(locale)) return
if (locale in activeLocaleFlushes) return activeLocaleFlushes[locale]
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
const queues = getLocalesQueues(locale)
// istanbul ignore if
if (queues.length === 0) return

// TODO what happens if some loader fails
activeLocaleFlushes[locale] = Promise.all(
queues.map(([locale, queue]) => {
return Promise.all(queue.map(loader => loader())).then(partials => {
removeLocaleFromQueue(locale)
partials = partials.map(partial => partial.default || partial)
addMessages(locale, ...partials)
})
})

// todo: what happens if some loader fails?
activeFlushes[locale] = Promise.all(
queues.map(([locale, queue]) => loadLocaleQueue(locale, queue))
).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) {
Expand Down
19 changes: 19 additions & 0 deletions test/runtime/includes/loaderQueue.test.ts
Expand Up @@ -50,3 +50,22 @@ test('consecutive flushes return the same promise', async () => {
expect(flushB).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')
})

0 comments on commit e560514

Please sign in to comment.