3
\$\begingroup\$

Problem 1

StratifiedJS allows people to cleanly create observable streams using the Stream class from sjs:sequence:

var {Stream} = require("sjs:sequence");

var integers = new Stream(function(emit)
{
    for(var i = 0; true; i++)
    {
        emit(i);
    }
});

Streams can then be iterated through using the each function:

var {Stream, each} = require("sjs:sequence");

var integers = new Stream(function(emit)
{
    for(var i = 0; true; i++)
    {
        emit(i);
    }
});

each(take(integers, 100), i=>console.log(i));

Asynchronous streams can be created the same way, and then iterated through synchronously using each, or listened to using spawn and each.

var {Stream, each} = require("sjs:sequence");

var integers_but_slowly = new Stream(function(emit)
{
    for(var i = 0; true; i++)
    {
        // 100 ms (0.1 seconds)
        hold(100);

        emit(i);
    }
});

// Asynchronous iteration of asynchronous stream.
spawn each(integers_but_slowly, i => console.log(i));

// Synchronous iteration of asynchronous stream.
each(integers_but_slowly, i => console.log(i));

This gives power to stream, but unfortunately removes power from the code that is iterating the stream; one can’t, for example, stop the iteration once it has started.

One could use the consume function, but it works very poorly when you decide you want to pause the stratum that is iterating the stream. For example, the following code, which seems would log all the integers smaller than 50 with an interval of 2 seconds between each, actually misses most of them:

var {Stream, consume} = require("sjs:sequence");

var integers_but_slowly = new Stream(function(emit)
{
    for(var i = 0; true; i++)
    {
        // 100 ms (0.1 seconds)
        hold(100);

        // Simulating the behavior of DOM event firing (it won’t wait for `emit` to finish before firing the next event).
        spawn emit(i);
    }
});

consume(integers_but_slowly, function(next)
{
    var i;
    while((i = next()) < 50)
    {
        console.log(i);
        hold(2000);
    }
});

The challenge is to create an iterator function that can be called on a stream to follow the iterator pattern. This iterator must have a buffer that holds the values that emit sends if the calls between next are longer than the calls to emit. Additionally, the emit function must return immediately.

Problem 2

As we saw above, creating an observable stream is really easy. Creating a stream of clicks shouldn’t be hard. However, there are some gotchas to be aware of. The naive approach would be to do the following:

new Stream(function(emit)
{
    window.addEventListener("click", emit);

    // This is an infinite stream, never terminate it.
    while(true)
    {
        hold(1000000);
    }
});

That works, but it does have flaws. Every time we try to iterate over it, it will attach a new event listener to window. These event listeners will never go away, and if our stream is used thoroughly throughout an application, it will cause unnecessary work and have more memory than necessary be allocated. Additionally, even if we do something more sophisticated there, we still have a problem with the while loop: the function will never terminate. It won’t be causing problems, but it will be taking up unnecessary space in the memory. Gladly, we can fix this very easily using waitfor/resume.

My solution

The main challenge I’ve set myself was to create a click counter that has a minimum delay between updates on the screen, yet, taking its time, eventually is able to catch up with the amount of times clicked. I wanted to have the following code work:

var c = 0;
var it = clicks.iterator();
while(true)
{
    document.body.textContent = String("You\u2019ve clicked " + c++ + " times.");
    hold(500);
    console.log(it.next());
}

This way, I’d allow people to iterate through the observable stream, doing something that pauses the stratum, and still not losing any elements. Additionally, they can very easily stop iterating the stream via break or a condition on the while loop.

This is my solution to the problem (the entire HTML page):

<!doctype html>
<html lang="en" role="application">
    <head>
        <meta charset="utf-8" />

        <title>Click Counter</title>

        <style>
            *
            {
                user-select: none;
                pointer-events: none;
                cursor: default;
            }

            html
            {
                min-height: 100vh;
                display: flex;
                align-items: center;
                justify-content: center;
                font-size: 8vmin;
            }

            body
            {
                text-align: center;
            }
        </style>

        <script src="https://code.onilabs.com/sjs/unstable/stratified.js"></script>
        <script type="text/sjs">
            {
                var {Stream, each} = require("sjs:sequence");

                var listeners;
                var clicks = new Stream(function(emit)
                {
                    if(!listeners)
                    {
                        listeners = [];

                        window.addEventListener("click", function(e)
                        {
                            for(var i = listeners.length; i > 0; i--)
                            {
                                var listener = listeners[i-1];
                                listener(e);
                            }
                        });
                    }

                    listeners.push(emit);

                    // This is an infinite stream, never terminate it.
                    waitfor()
                    {
                    }
                });

                clicks.iterator = function()
                {
                    var buffer = [];
                    var subscribe = v => buffer.push(v);
                    spawn each(clicks, v => void(subscribe(v)));
                    return(
                    {
                        next: function()
                        {
                            if(buffer.length)
                            {
                                return(buffer.shift());
                            }
                            else
                            {
                                var ret = false;
                                var n;
                                waitfor()
                                {
                                    subscribe = function(v)
                                    {
                                        if(ret)
                                        {
                                            buffer.push(v);
                                        }
                                        else
                                        {
                                            n = v;
                                            ret = true;
                                            resume();
                                        }
                                    }
                                }
                                return(n);
                            }
                        }
                    });
                }

                var c = 0;
                var it = clicks.iterator();
                while(true)
                {
                    document.body.textContent = String("You\u2019ve clicked " + c++ + " times.");
                    hold(500);
                    console.log(it.next());
                }
            }
        </script>
    </head>
    <body></body>
</html>

Feedback on anything is appreciated; while this is mainly about the StratifiedJS code, feedback on the CSS, HTML, and whatever else is also welcome.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.