kefirjs / kefir

A Reactive Programming library for JavaScript
https://kefirjs.github.io/kefir/
MIT License
1.87k stars 97 forks source link

Extending the library #177

Closed pedromsilvapt closed 6 years ago

pedromsilvapt commented 8 years ago

So, there are some methods that I would use on my application in a lot of places, and for convenience, I would like to extend the prototype so I could just call them like I call regular methods on streams.

Based on this issue #46 I figured I had to extend the Kefir.Observable prototype. So, I added the following code at the beginning of the application (ES2015, the function itself is not important, just de definition):

import Kefir from 'kefir';
import extend from 'extend';

Kefir.Observable.prototype.joinBy = function ( glue, options = {} ) {
    options = extend( { flushOnEnd: true }, options );

    let buffer = [];
    let cursor = 0;

    return this.withHandler( ( emitter, event ) => {
        //console.log( buffer.join( '' ) );
        if ( event.type == 'value' ) {
            buffer.push( event.value );

            if ( event.value == glue[ cursor ] ) {
                cursor++;

                if ( cursor == glue.length ) {
                    emitter.emit( buffer );
                    buffer = [];
                    cursor = 0;
                }
            } else {
                cursor = 0;
            }
        } else if ( event.type == 'end' ) {
            if ( options.flushOnEnd && buffer.length > 0 ) {
                emitter.emit( buffer );
            }

            emitter.end();
        } else {
            emitter.emitEvent( event );
        }
    } );
};

Then, somewhere ahead in my code I have:

stream.flatten( s => s.replace( /\r\n/gi, '\n' ).split( '' ) )
    .joinBy( [ '\n', '\n' ] )
    .map( s => s.join( '' ) )

The varriable stream can either be, depending from the circumstances, from Kefir.sequentially or Kefir.stream. Strangely, the former works, while the later, Kefir.stream, throws an error saying that joinBy is not defined.

Is there any way to extend the library, and if there is, would it be possible to maybe create a method extend that could perhaps receive a method name and a function as parameters, to add that extension to the prototype?

rpominov commented 8 years ago

The varriable stream can either be, depending from the circumstances, from Kefir.sequentially or Kefir.stream. Strangely, the former works, while the later, Kefir.stream, throws an error saying that joinBy is not defined.

Hm, that's weird. It should work fine in both cases. Are you sure you add method to the prototype before trying to call it? Can you build a failing example on jsfiddle?

pedromsilvapt commented 8 years ago

No, but I found the issue and it was not related to kefir. Apparently, the error would trigger only if the stream was created in a specific file of my code. I checked, and there was one import where I was calling the module with an uppercase, like import 'Kefir'. Because the strings weren't exactly the same, node was returning me a different instance of the module, one which didn't have the joinBy in it's prototype. Windows with being case-insensitive!

You can close the issue, sorry for wasting your time.

PS: Do you think it would be a good idea to put somewhere in the documentation how to add your own methods to the prototype? I only discovered about Kefir.Observable.prototype because I stumbled upon it in an issue.

rpominov commented 8 years ago

Yeah, it probably makes sense to mention in docs that Kefir.Observable, Kefir.Stream, and Kefir.Property are available and one can add methods to their .prototype's.

I'll try to write something later, but if someone wants to submit a PR, that would be nice too. I'm thinking of another small section like these at the end: image

boneskull commented 8 years ago

Along these lines, I had a question if this was a fair implementation of reject()? Just wanted to be sure I wasn't missing something.

import {Observable} from 'kefir';
import _ from 'lodash';

Observable.prototype.reject = function reject (func = _.identity) {
  return this.withHandler((emitter, event) => {
    if (event.type === 'value') {
      if (!func(event.value)) {
        emitter.emit(event.value);
      }
      return;
    }
    emitter.emitEvent(event);
  });
};

When the docs get updated, it'd be really helpful to see an example or two.

Thanks!

rpominov commented 8 years ago

It's like inverted filter? Looks good to me.

Totally forgot about the docs, sorry. Would appreciate a PR from someone!

mAAdhaTTah commented 6 years ago

After the discussion on #255, we recommend creating a function for this and using it with Kefir#thru instead of extending the built-ins. The documentation for thru is available here, which speaks a little bit to its use case. Do we want to make the documentation more explicit or can we close this issue?