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;
});
};