1

I don't like anything I've seen so far...

Module

import lineReader from 'line-reader'

let bigFile = './huge-file.csv'

export default class DooDad {

    constructor() {
        this.dictionary = new Map()
        // Something like this seems like it'd be nice...
        // await this.load()
    }

    load() {
        return new Promise((resolve, reject) => {
            lineReader.eachLine(bigFile, (line, last) => {
                // this.dictionary = The contents of huge-file.csv
            })
        })
    }

    doStuff(foo) {
        // Uses this.dictionary to do something interesting
        // Problem: Unusable without first calling and waiting for this.load()
    }
}

Usage

import DooDad from '../doodad'

let doodad = new DooDad()

// We have to first call and wait for load() before doodad is useable
doodad.load().then(x => {
    doodad.doStuff()
})

Seems like you'd either want to...

1) Make the loading synchronous

2) Make a static create function on DooDad that returns a promise that resolves to a new instance of a DooDad

3) Make the constructor return a Promise (seems weird) Asynchronous constructor

4) Emit an event when its done loading

5) Leave it how it is

6) ????

Jason
  • 11,435
  • 24
  • 77
  • 131

1 Answers1

2

doodad.load().then() makes perfect sense to me. You don't want constructors to be async so it makes sense to have .load() be where the async stuff is.

The other pattern I've seen is you export a factory-type function that returns a promise and when that promise is resolved, the resolved value is your fully formed object. This has the advantage of there is no access to the object until the async stuff is done and there is no temptation by calling code to try to use it before it is ready.

import makeDooDad from '../doodad'

makeDooDad().then(doodad => {
   // you only get access to the object here after it's been fully
   // initialized
   doodad.doStuff();
});

And, the makeDooDad() factory function does something like this inside of your module:

function makeDooDad() {
   let d = new DooDad();
   // fully initialize the doodad object before resolving the promise
   // and before returning the object
   return d.load().then(() => {
       // let the object itself by the resolved value
       return d;
   });
}

As for your other options:

Make the loading synchronous

This could be OK only if it is only done at server startup time. There is generally no real cost to doing some synchronous I/O at server startup time and often it makes things a lot simpler. For example, require() itself does synchronous I/O.

Make a static create function on DooDad that returns a promise that resolves to a new instance of a DooDad

That is essentially what I recommended above with the factory function. This is often times a good option.

Make the constructor return a Promise (seems weird) Asynchronous constructor

No. Don't really want to do that. Constructors should return objects, not promises. Use a factory function to return a promise.

Emit an event when its done loading

There are other pieces of code that do this such as creating a writeStream, emits an open event on the stream when the stream is actually open. But, in the days of promises, this isn't my favorite way of doing things for other types of objects that aren't already using lots of events.

Leave it how it is

It's OK as is, but I prefer the factory function that returns a promise.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Good points. I like sync and when that doesn't make sense I'm sold on the factory based on `This has the advantage of there is no access to the object`. – Jason Sep 28 '17 at 20:24
  • @Jason - If this answers your question, then you can indicate that to the community by clicking the checkmark to the left of the answer and also earn yourself some reputation points for following the proper procedure here on stack overflow. – jfriend00 Sep 28 '17 at 20:37