Search code examples
javascriptcyclejs

Cycle.js: Custom driver not emitting any event inside main()


I'm creating a custom cycle.js driver with the purpose of preloading images. The main function exports a sink stream imagesToLoad emitting an array of image URLs. Those are then loaded by the driver and should return a stream that emits when the images are finished loading.

Problem

I can subscribe to the stream returned by the driver inside the driver's code and see that the loaded images are indeed emitted but never when mapping from inside the main function.

I am probably doing something wrong but I cannot see what exactly.

The driver

import { from, Subject } from 'rxjs'
import { adapt } from '@cycle/run/lib/adapt'

const loadImage = (src) =>
    new Promise((resolve, reject) => {
        const img = new Image()
        img.addEventListener('load', () => resolve(img))
        img.addEventListener('error', (err) => reject(err))
        img.src = src
    })

export const makeImageLoaderDriver = () => {

    const imageLoaderDriver = (sink$) => {
        const imageCache = {}
        const source$ = new Subject()

        from(source$).subscribe({
            next(images) {
                const imagesPromises = images.map((src) => {
                    const imgSuccess = { src, loaded: true }
                    if (!!imageCache[src]) return Promise.resolve(imgSuccess)
                    return loadImage(src)
                        .then(() => {
                            imageCache[src] = imgSuccess
                        })
                        .catch((error) => {
                            imageCache[src] = { src, loaded: false, error }
                        })
                })

                Promise.all(imagesPromises).then(() => {
                    source$.next(imageCache)
                })
            },
        })

        return adapt(source$)
    }

    return imageLoaderDriver
}

The index.js

const drivers = {
    imagesToLoad: makeImageLoaderDriver(),
}

run(main, drivers)

The main function

export function main(sources) {

    sources.imagesToLoad.pipe(map(console.log)) // THIS NEVER LOGS ANYTHING

    return {
        imagesToLoad: of([ imageUrl1, imageUrl2 ]),
    }
}

Solution

  • sources.imagesToLoad.pipe(map(console.log)) creates a new Observable, but this Observable is not named (as a const) and is not used anywhere else. It's just dropped and never subscribed to, so effectively that line in main does nothing.

    It needs to get plugged into one of the other stream pipelines that will some way end up in the sink. For instance, get the output of that operation, name it as a const, and then use that const stream in another stream pipeline, and this stream pipeline must eventually end up in a sink.

    To debug, you can put .subscribe() after the map, and most likely you will see the console.log happen. Just don't leave the subscribe() in the code because Cycle.js is a framework where you don't ever need to subscribe on the main side, because subscriptions are meant for the drivers side.

    Notice also that your driver takes sink$ as input but it never uses that. You should subscribe to sink$ and perform effects based on its emissions.