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