3

I have many promises that have a resulting value I need to export. This value is in fact a connection to an embedded database.

The problem is I have several middleware promises that work on the connection before resolving the final db resource. I do this for each collection in the DB.

If I use the method many times I am creating several connections to this DB with many copies of its own middleware which can grow rather large. How Do I export this promise and all the others in one final object?

Should I create a singleton class that in the constructor resolves the promises and has the collection items as public variables? Or should I some how resolve to export them in an object?

Edit - show example promise. One of many

This is just one of the collection connections. Notice that it creates a new collection when this method is called. An in memory binary tree is created with the IndexLoader for each collection.

This is why I wish to resolve this method and return place the result which is the collection into an object to be used there. If I were to call this method all over the application then it would be creating a new binary tree each time.

export const Users = (): Promise<Collection> => {
  return new Promise<Collection>((resolve, reject) => {
      const User: Collection = new Collection("Users");
      const indexPromises: Array<Promise<any>> = [];
      const uniqIndices: string[] = ["username"];

      User.pre("save", setDefaults);
      User.pre("update", updateDefaults);

      uniqIndices.forEach((v) => {
        indexPromises.push(IndexLoader(User, {fieldName: v, unique: true}));
      });

      Promise.all(indexPromises)
          .then(() => {
              resolve(User);
          })
          .catch(reject);
  });
};

Edit 2 - what I meant by singleton resolution export

I feel like this use of a singleton is sound.

import {Users} from "./file"; // this is the method above
export interface IDB {
    Users: Collection;
}
export class DB implements IDB {
    public static getInstance() {
        if (!DB.instance) {
            DB.instance = new DB();
        }
        return DB.instance;
    }
    private static instance: DB;
    public Users: Collection;
    
    constructor() {
        Users()
          .then((res) => {
              this.Users = res;
          })
          .catch(/** error handler */);
     }
}

Using class in other file to interact with same connection and binary tree

import {DB} from "..";
let db = DB.getInstance();
db.Users.insert({/** new object */})
    .then()
    .catch()

!! the singleton is tried and does not work !!

Community
  • 1
  • 1
mjwrazor
  • 1,866
  • 2
  • 26
  • 42
  • *"If I use the method many times…*" - Do you have a promise, or do you have a function ("method") that returns a promise? Posting your code (possibly promise-less or non-working) would help a lot. – Bergi Jul 17 '17 at 18:23
  • "*I have many promises that have a resulting value I need to export.*" - you cannot export that value (asynchronously). But you can just export the promise for it! – Bergi Jul 17 '17 at 18:25
  • "*Should I create a singleton class that in the constructor resolves the promises*" - no, [absolutely don't do that](https://stackoverflow.com/q/24398699/1048572). – Bergi Jul 17 '17 at 18:26
  • 1
    Regarding the example code, you should avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it), but otherwise it seems fine. – Bergi Jul 17 '17 at 18:38
  • 1
    @Bergi the issue though is that this creates a `new` collection each time the method is called and used. Which will not utilize the same memory as another execution of this method. Which the collection creates an in memory binary tree for each index. Also how would you suggest avoiding the antipattern here? where there usually are many `IndexLoader`s that need to be resolved. – mjwrazor Jul 17 '17 at 18:43
  • OK, if you don't want to re-execute the method every time you can just memoize the result, or directly export the promise without lazy initialisation. – Bergi Jul 17 '17 at 19:01
  • I guess I don't understand what you mean by memorize the result. – mjwrazor Jul 17 '17 at 19:05
  • Looking over the antipattern more I realized what was happening. And have resolved the problem. – mjwrazor Jul 17 '17 at 20:19
  • I meant *memoise*, like in [this example](https://stackoverflow.com/a/31820876/1048572), not "memorize". Basically what you were trying to do with the singleton - only that `class` syntax overcomplicates everything (you don't need any objects for it), and that assigning `this.Users` from an asynchronous callback cannot work. – Bergi Jul 17 '17 at 20:45

1 Answers1

1

The result is contained in a promise, if I understand you. So, you should simply return the object that contains the open connection which will then wrap it in a resolved promise (then) and you should be able to do things within then.

Here's an example in rough javascript that I threw together.

var p1 = new Promise((res, rej) => { 
   res( something_to_get_my_connection(input) ); //resolve the promise with the connection
}).then( (con) => {
   //Use connection. 
});

If you mean that you want it outside of the promise construct, I've seen this in the past:

let myEstablishedConnection = null;
var p2 = new Promise((res, rej) => { 
   res( something_to_get_my_connection(input) ); //resolve the promise with the connection
}).then( (con) => {
   //Use connection. 
   myEstablishedConnection = con;
   continueProgram(); //This can be several things but I'm assuming the remaidner of the implementation is here. 
});

And without the outer scope variable which is generally frowned upon:

var p3 = new Promise((res, rej) => { 
   res( something_to_get_my_connection(input) ); //resolve the promise with the connection
}).then( (con) => {
   continueProgram(con); //This can be several things but I'm assuming the remainder of the implementation is started here. 
});

To wait for all promises, you then use something along the lines of:

Promise.all([p1,p2, p3]).then( () => { proceed(); } ); 

(These examples are off the top of my head, I apologize if there is a typo or inconsistency. The promise.all usage is obviously contrived.)

Frank V
  • 25,141
  • 34
  • 106
  • 144
  • Using an outer-scope `myEstablishedConnection` variable is a horrible antipattern. Don't do this. – Bergi Jul 17 '17 at 18:28
  • Perhaps and when spoke about in theory I agree, but I've seen it used to great success and clean implementations in some NodeJs packages. One that immediately comes to mind is TestCafe. – Frank V Jul 17 '17 at 18:30
  • I didn't say that it could not work fine, I just said that it's a horrible pattern and [there are much better solutions](https://stackoverflow.com/q/28250680/1048572). – Bergi Jul 17 '17 at 18:33
  • I understand but what I'm saying is that when packaged carefully and used thoughtfully, it's not as a horrible a pattern as it appears when being discussed in a grander context. – Frank V Jul 17 '17 at 18:38