Typolino – Web Worker Revisited

The first iteration of the web worker was OK and did the job. But somehow it didn’t feel good enough. I wanted to rewrite it using RxJs. The main reason being that with RxJs I have built in functionality to control concurrency. I don’t want a lesson with a lot of images to go crazy. Therefore I decided to rewrite everything and (altough painful) passing all data always down the stream. Not sure if this would be considered good practice, but I wanted to try it out. It seems quite natural to use tap() and / or access variables that are somewhere in the scope from an operator – but if you think about proper decomposition, purity and testability..

/// <reference lib="webworker" />

import { Lesson } from './lesson';
import { environment } from '@typolino/environments/environment';
import * as firebase from 'firebase/app';
import 'firebase/storage';
import 'firebase/auth';

import { from, of, forkJoin } from 'rxjs';
import {
  mergeMap,
  switchMap,
  map,
  withLatestFrom,
  delay,
} from 'rxjs/operators';

const firebaseConfig = environment.firebase;
firebase.initializeApp(firebaseConfig);

addEventListener('message', ({ data }) => {
  const lesson = data as Lesson;

  from(lesson.words)
    .pipe(
      withLatestFrom(of(lesson)),
      mergeMap(
        ([word, lesson]) =>
          from(
            firebase
              .storage()
              .ref(`${lesson.id}/${word.imageId}`)
              .getDownloadURL()
          ).pipe(withLatestFrom(of(word))),
        5 // concurrency
      ),
      mergeMap(([downloadUrl, word]) =>
        from(
          fetch(downloadUrl, {
            mode: 'no-cors',
            cache: 'default',
          })
        ).pipe(withLatestFrom(of(downloadUrl), of(word)))
      )
    )
    .subscribe(([response, downloadUrl, word]) => {
      word.imageUrl = downloadUrl;
      postMessage({
        imageId: word.imageId,
        imageUrl: downloadUrl,
      });
    });
});

It does almost the same as before, but I had to rewrite some parts. The good thing is that we can control concurrency now and add delays etc. as we wish.

Processing the messages has changed a bit too, as we don’t have the index anymore. I don’t think it is terribly inefficient, but could be improved:

 worker.onmessage = ({ data }) => {
          from(lesson.words)
            .pipe(
              withLatestFrom(of(data)),
              filter(([word, data]) => word.imageId === data.imageId)
            )
            .subscribe(([word, data]) => {
              word.imageUrl = data.imageUrl;
            });
        };

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.