1

I have this code which just reads in data from a .csv file and converts it to json and logs the data:

const fs = require('fs');
const path = require('path');

const sd = path.resolve(__dirname + '/fixtures/SampleData.csv');
const strm = fs.createReadStream(sd).setEncoding('utf8');

const Rx = require('rxjs/Rx');
const csv2json = require('csv2json');


const dest = strm
  .pipe(csv2json({
    separator: ','
  }));

dest.on('error', function(e){
    console.error(e.stack || e);
})

const obs = Rx.Observable.fromEvent(dest, 'data')
          .flatMap(d => Rx.Observable.timer(100).mapTo(d))

obs.subscribe(v => {
    console.log(String(v));
})

What the code is doing is logging all the data after a 100 ms delay. I actually want to delay on each line of data and log each line after a small delay.

The above code doesn't achieve that - what is the best way to control the rate at which the data is logged?

Hypothesis: All the lines of data come in approximately at the same time, so all are delayed 100 ms, so they end up getting printed at pretty much the same time. I need to only start delaying the next line after the previous as been logged.

the following code seems to do the same thing as using the timer above:

const obs = Rx.Observable.fromEvent(dest, 'data')
      .delay(100)
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817

2 Answers2

2

Hypothesis: All the lines of data come in approximately at the same time, so all are delayed 100 ms, so they end up getting printed at pretty much the same time. I need to only start delaying the next line after the previous as been logged.

Your hypothesis is correct

Solution

Swap out the .flatMap() in your original solution with .concatMap()

Rx.Observable.from([1,2,3,4])
  .mergeMap(i => Rx.Observable.timer(500).mapTo(i))
  .subscribe(val => console.log('mergeMap value: ' + val));

Rx.Observable.from([1,2,3,4])
  .concatMap(i => Rx.Observable.timer(500).mapTo(i))
  .subscribe(val => console.log('concatMap value: ' + val));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.0.3/Rx.js"></script>

This will ensure that every emission completes before the next emission is subscribed to and starts delaying its value.

Mark van Straten
  • 9,287
  • 3
  • 38
  • 57
  • thanks Mark! So you're saying the mergeMap and concatMap solutions should both work. Can you speak briefly as to the difference between the two? Why do they both work? – Alexander Mills Feb 02 '17 at 22:54
  • I have explained the difference between flatMap concatMap and switchMap in an other answer http://stackoverflow.com/a/42006671/106909 – Mark van Straten Feb 02 '17 at 23:30
  • 1
    In your case you would need to use concatMap, run the snippet in my answer and observe the difference in how values arrive in the subscription – Mark van Straten Feb 02 '17 at 23:31
0

I couldn't find the functionality I needed in the RxJS library (although it might be there, I just couldn't find it, let me know if there is a better, more idiomatic, way).

So I wrote this, which seems to do the job:

const fs = require('fs');
const path = require('path');

const sd = path.resolve(__dirname + '/fixtures/SampleData.csv');
const strm = fs.createReadStream(sd).setEncoding('utf8');

const Rx = require('rxjs/Rx');
const csv2json = require('csv2json');

const p = Rx.Observable.prototype;

p.eachWait = function(timeout){

    const source = this;
    const values = [];
    let flipped = true;

    const onNext = function (sub){

          flipped = false;

          setTimeout(() => {

            var c = values.pop();
            if(c)  sub.next(c);

            if(values.length > 0){
               onNext(sub);
            }
            else{
               flipped = true;
            }

         }, timeout);
    }

      return Rx.Observable.create(sub => {

          return source.subscribe(

                function next(v){

                         values.unshift(v);

                         if(flipped){
                             onNext(sub);
                         }

                 },
              sub.error.bind(sub),
              sub.complete.bind(sub)
          );

      });

}


const dest = strm
  .pipe(csv2json({
    separator: ','
  }));

dest.on('error', function(e){
    console.error(e.stack || e);
});

const obs = Rx.Observable.fromEvent(dest, 'data')
      .eachWait(1000)

obs.subscribe(v => {
  console.log(String(v));
});

I assume this is as about as performant as you can make it - only one timer should be running at any given moment.

Alexander Mills
  • 90,741
  • 139
  • 482
  • 817