skip to Main Content

I’m writing an app that manages playlists.
Basically, my actual logic looks like

//define playlist props
class Playlist{
    public tracks = [];
}

class ApiPlaylist extends Playlist{
    //fill playlist withs (single page) data from API
    public async loadPage(paginationSettings){
        const pageTracks = await...
        this.tracks = [...this.tracks,...pageTracks]; //concat tracks
    }
}

class Paginated extends ApiPlaylist{
    private iterations = 0;
    private endReached = false;
    public async loadAll(){
        while (!this.endReached){
            this.loadNext();
        }
    }
    public async loadNext(){
        this.iterations++;
        await this.loadPage(); //call ApiPlaylist method
        if(...){
            this.endReached = true; //stop iterating
        }
    }

}

const playlist = new Paginated();
playlist.loadAll();

It works.

But what if I have different others paginated datas to get, that are not related to playlists ?

I would like to use the mechanism from PaginatedPlaylist with another classes, without having to duplicate it.

Acually, Paginated extends ApiPlaylist.
Is there a simple way to implement the methods from Paginated to ApiPlaylist without using extends ?

Something like

class ApiPlaylist [implements] Paginated{}
class ApiPost [implements] Paginated{}
class ApiPage [implements] Paginated{}

Thanks for your help !

2

Answers


  1. As it already has been pointed out, the correct pattern to use is mixin or trait based composition.

    The next provided example code demonstrates the usage of a function-based mixin which implements and applies the feature/behavior of "loadable tracks".

    async function mockedFetch(paginationSettings) {
      console.log({ paginationSettings });
    
      console.log('... fetching ...');
    
      return new Promise(
        resolve => setTimeout(resolve, 1_500, ['foo', 'bar', 'baz']),/*
        reject => { handle reject },*/
      );
    }
    
    
    // implementation of the actual context-aware `loadPage` method.
    async function fetchTracksAndPushIntoBoundTrackList(paginationSettings) {
      const pageTracks = await mockedFetch(paginationSettings);
    
      this.tracks.push(...pageTracks);
    }
    // function based mixin.
    function withLoadableTracks() {
      this.loadPage = fetchTracksAndPushIntoBoundTrackList.bind(this);
    }
    
    
    // base class.
    class Playlist {
      constructor () {
    
        this.tracks = [];
      }
    }
    // sub-classing ...
    class ApiPlaylist extends Playlist {
      constructor () {
    
        // inheritance.
        super();
    
        // composition ... applying the function based mixin.
        withLoadableTracks.call(this);
      }
    }
    
    class ApiPost extends Playlist {
      constructor () {
        super();
        withLoadableTracks.call(this);
      }
    }
    class ApiPage extends Playlist {
      constructor () {
        super();
        withLoadableTracks.call(this);
      }
    }
    
    
    (async () => {
    
      const apiPlaylist = new ApiPlaylist;
    
      console.log({ tracks: apiPlaylist.tracks });
    
      await apiPlaylist.loadPage({ tracks: 3 });
    
      console.log({ tracks: apiPlaylist.tracks });
    
      await apiPlaylist.loadPage({ tracks: 3 });
    
      console.log({ tracks: apiPlaylist.tracks });
    
    })();
    .as-console-wrapper { min-height: 100%!important; top: 0; }
    Login or Signup to reply.
  2. There’s a pattern for this in other OO languages called CRTP (curiously recurring template pattern), and you can do it in JavaScript with a function that creates classes.

    I think it’s a lot easier to understand than the various ways of doing mixins, and causes fewer problems:

    function PaginateClass(Base) {
        class Paginated extends Base {
            paginatedMethod() {
                return `paginated ${this.baseMethod()}`;
            }
        }
        return Paginated;
    }
    
    class Base {
        baseMethod() {
            return 'baseMethod';
        }
    }
    
    const PaginatedBase = PaginateClass(Base);
    
    const instance = new PaginatedBase();
    
    console.log(instance.paginatedMethod());
    .as-console-wrapper { min-height: 100%!important; top: 0; }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search