Synchronized events

Synchronized events
Photo by Angela Loria / Unsplash

I was writing some scripts to attach i18next to Bubble. Long story short, we have to ensure the library is loaded, i18next is initialized before rendering the page. I handle this by creating a custom component to check whether i18next is ready, optionally load some more namespaces, finally expose an is_ready state. The remainder of the page should only be visible when the Loader component says everything is kosher.

The problematic thing is resources won't load in order. The ideal flow for the whole thing is:

  1. Page load
  2. i18next library is loaded
  3. i18next is initialized
  4. The loader component is instantiated
  5. The loader check for i18next is instantiated (should be yes), load some more namespaces, then tell everything else that it is ready.
  6. The remainder of the page is rendered

The problem is that we have a race condition here. 4 can happen before 2 and 3. We gotta put more check into the loader function.

export function deferredPromise<T>(): Promise<T> & {
  resolve: (v: T) => void;
  reject: (err: any) => void;
} {
  let res;
  let rej;
  const promise = new Promise<T>((resolve, reject) => {
    res = resolve;
    rej = reject;
  });

  // @ts-ignore
  promise.resolve = res;
  // @ts-ignore
  promise.reject = rej;
  // @ts-ignore
  return promise;
}
export async function loadNamespace(ns: string[]): Promise<any> {
  if (!i18next.isInitialized) {
    const deferred = deferredPromise();
    i18next.on("initialized", () => deferred.resolve(true));
    i18next.on("failedLoading", (_lng, _ns, msg) => deferred.reject(msg));
    await deferred;
  }
  return i18next.loadNamespaces(ns);
}

The cool thing here is in the if section above. Essentially we are "synchronizing" the event listener. It is pretty jarring but it works. Pretty weird