1
\$\begingroup\$

I usually do not create Streams, but it seems that JavaScript doesn't have a built-in one and I figured I would do it for a personal project I am working on. This code seems to work as intended and does what I want just fine, but I have concerns mainly about how often I am checking my buffer. I am mainly looking for a recommendation about how often I should check to see if any data has been written to my buffer. If there are any other improvements/suggestions, I am open to hearing them!

Edit: After thinking about this more, I realized I could do this as event-driven instead and let the system/browser handle the blocking. I left both functions in so people can weigh in on either one. However, I would still like input on the first approach for general knowledge.

const StreamPrototype = {
    _data: [],
    _callbacks: [],

    readAsync: async function* () {
        while(await sleep(25)) {
            let data;
            while((data = this._data.pop()))
                yield data;
        }
    },

    onData: function(callback) {
        if (callback instanceof Function)
        this._callbacks.push(callback);
    },

    push: function(data) {
        if (data) {
            this._pushOntoStack(data);
        }
    },

    _pushOntoStack: async function(data) {
        return new Promise(resolve => {
            this._data.push(data); // Uses Sleeps
            this._callbacks.forEach(item => { // Pushes data out using event-driven
                item(data);
            });
        });
    }
};

// This was just copied from the internet
const sleep = time => new Promise(res => setTimeout(res, time, "done sleeping"));
\$\endgroup\$
4
  • 3
    \$\begingroup\$ I really, really, really wish upon a star, a 4-leaf clover, and those red ruby slippers that downvotes came with explanation. Downvoting is not a popularity contest and therefore demands elucidation. \$\endgroup\$
    – radarbob
    Commented Jan 15, 2023 at 16:09
  • \$\begingroup\$ You might want to explain what you mean by "streams" because if you're talking about data streams, both browser and node have Web Streams API. If you mean Java-like streams, the closest you get are array methods. But it seems like what you call "streams" is really just an event emitter, which Node.js has and browser JS also has if you extend EventTarget. \$\endgroup\$
    – Joseph
    Commented Jan 17, 2023 at 16:13
  • \$\begingroup\$ @Joseph so mainly I am wanting a memory stream. Ultimately what my goal, let me preface this with I’m learning react, to have a websocket in component A have an display in component B and pass data received in the ws in A to B, since B should be handling all the displaying needs. \$\endgroup\$
    – Matthew
    Commented Jan 18, 2023 at 22:04
  • \$\begingroup\$ What about observables ? You could either implement your own Observer pattern or use the RxJS library linked above, which gives great flexibility. \$\endgroup\$ Commented Jan 21, 2023 at 10:53

1 Answer 1

1
\$\begingroup\$

This doesn't look like a nice approach. For one, you don't really need that 25ms waiting like polling.

So assuming that you're working with WebSockets like

// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');

// Listen for messages
socket.addEventListener('message', (event) => {
    console.log('Message from server ', event.data);
});

You may merge the async "message" event listener and others like "error", "open", "close" etc, under one async iterable and consume with for await of loop as follows. Note that we don't need to use an async generator here since we are yielding promises.

function wsMessageStream(url){
  let _v, // previous resolve
      _x, // previous reject
      p  = new Promise((v,x) => (_v = v, _x = x)),
      ws = new WebSocket(url);

  function* emitterGen(ws){
    ws.addEventListener( "open"
                       , _ => ( _v({data: `Esatablished WebSocket connection for ${url}.`})
                              , p = new Promise((v,x) => (_v = v, _x = x))
                              )
                       );
    ws.addEventListener( "close"
                       , _ => ( _v({data: `Closed WebSocket connection for ${url}.`})
                              , _v = null
                              , _x = null
                              )
                       );
    ws.addEventListener( "message"
                       , m => ( _v(m)
                              , p = new Promise((v,x) => (_v = v, _x = x))
                              )
                       );
    ws.addEventListener( "error"
                       , e => ( _x(e)
                              , p = new Promise((v,x) => (_v = v, _x = x))
                              )
                       );
    while (_v || _x){
      try{
        yield p;
      }
      catch(e){
        console.log(e);
      }
    }
    return Promise.resolve(null); // finalize the iterable
  }
  return { stream: emitterGen(ws)
         , socket: ws
         };
}

let {stream,socket} = wsMessageStream('wss://ws.postman-echo.com/raw'),
    counter = 0;
setTimeout(_ => socket.close(), 5000);

(async function(){

  for await (let msg of stream){
    console.log(`Message received: ${msg.data}`);
    // run msg handlers here
    socket.readyState === 1 && socket.send(`Thank you. ${counter++}`);
  }
})();

\$\endgroup\$
4
  • \$\begingroup\$ If I did want to poll, which obviously an event-driven approach is better in this case, what would be an acceptance level of polling? I figured I would want it to be rather responsive. \$\endgroup\$
    – Matthew
    Commented Feb 2, 2023 at 5:07
  • 1
    \$\begingroup\$ @Matthew If you would like to use pollling then the period would depend on several factors like how reactive your server is or how often you expect to update the display. For instance in this example I am using an echo server which just echoes the received message. Throughout it's 5000ms lifetime it makes ~33 message exchanges so every message exchange takes like 150ms. In this particular case polling in shorter intervals after your request would be wasteful. In case server sends you messages without you requesting then you may consider a period = how often you need to refresh your display. \$\endgroup\$
    – Redu
    Commented Feb 2, 2023 at 7:54
  • 1
    \$\begingroup\$ @Matthews Say if your server fetches some live data from another server (such as live stock exchange data) and forwards it to the registered clients then at each client side you may measure the intervals of received messages and continuously calculate a moving average to update polling period dynamically. Yet this streamifying approach relieves you from all such work. \$\endgroup\$
    – Redu
    Commented Feb 2, 2023 at 11:00
  • \$\begingroup\$ Thank you, I wasn’t thinking about other processes that may occur to affect how long it may take just for a response to be reached. Thank you for that insight, it was a great explanation! \$\endgroup\$
    – Matthew
    Commented Feb 3, 2023 at 14:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.