0

This is the function that I have:

let counter = 0;
let dbConnected = false;

async function notASingleton(params) {
    if (!dbConnected) {
        await new Promise(resolve => {
            if (Math.random() > 0.75) throw new Error();
            setTimeout((params) => {
                dbConnected = true; // assume we use params to connect to DB
                resolve();
            }, 1000);
        });
        return counter++
    }
};
// in another module that imports notASingleton
Promise.all([notASingleton(params), notASingleton(params), notASingleton(params), notASingleton(params)]);

or

// in another module that imports notASingleton
notASingleton(params);
notASingleton(params);
notASingleton(params);
notASingleton(params);

The problem is that apparently the notASinglton promises in might be executed concurrently and assuming they are run in parallel, the execution context for all of them will be dbConnected = false.

Note: I'm aware that we could introduce a new variable e.g. initiatingDbConnection and instead of checking for !dbConnected check for !initiatingDbConnection; however, as long as concurrently means that the context of the promises will be the same inside Promise.all, that will not change anything.

The pattern can be properly implemented in e.g. Java by utilizing the contracts of JVM for creating a class: https://stackoverflow.com/a/16106598/12144949

However, even that Java implementation cannot be used for my use case where I need to pass a variable: "The client application can’t pass any argument, so we can’t reuse it. For example, having a generic singleton class for database connection where client application supplies database server properties." https://www.journaldev.com/171/thread-safety-in-java-singleton-classes-with-example-code

Note 2: Another possibly related issue: https://eslint.org/docs/rules/require-atomic-updates#rule-details

  • JavaScript is single-threaded so there is no race condition. To see how singleton pattern can be implemented have a look in this [excellent book](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript) – watofundefined Nov 18 '19 at 17:22
  • @watofundefined That was my understanding, but MDN note is suggesting otherwise. Perhaps `Promise.all` implementation at the native level might be copying the the execution context for each promise and then run them in parallel – Houman Kamali Nov 18 '19 at 17:28
  • I see what you mean now - so my understanding of what MDN says there is that you don't know if runtime will **start** executing those async functions in the same order as they are in the array. There's still just one main thread though - if you want to go deeper into how it works, then read about [EventLoop](https://developer.mozilla.org/en/docs/Web/JavaScript/EventLoop). I'll post you some example code that makes sure that you initiate the connection to DB just once. – watofundefined Nov 18 '19 at 17:43
  • @watofundefined Yeah; that's what I'm thinking it is trying to say (just poorly worded), but there are this little nuances about how copying variables works; e.g. this: https://exploringjs.com/es6/ch_modules.html#_in-commonjs-imports-are-copies-of-exported-values – Houman Kamali Nov 18 '19 at 17:48
  • @Bergi Thanks! Still I'm not sure what happens to the context of the promise calls though? Are they run in the same thread, or in different threads? Actually, I think my question is not limited to Promise.all; what happens if we have multiple promise calls lexically after one another: `promise(); promise(); promise(); promise();` How is the execution context for each one of them created? – Houman Kamali Nov 18 '19 at 18:02
  • Here's [spec](https://www.ecma-international.org/ecma-262/6.0/#sec-promise.all) if MDN is not enough. + I recommend reading about the event loop - it's hard to explain certain things in SO comments – watofundefined Nov 18 '19 at 18:05
  • In that other module I would try doing: `await notASingleton(params); // this runs first` `notASingleton(params); // this runs second` – watofundefined Nov 18 '19 at 18:08
  • @watofundefined Thanks for the link! I updated my question to include a more generic case where the promises are called out of `Promise.all` I think the issue is still the same. Is the code inside each different instance of a Promise executed in parallel? If so, how and when is each the value of each variable read and updated? – Houman Kamali Nov 18 '19 at 18:10
  • @HoumanKamali "*what happens if we have multiple promise calls lexically after one another*" - they are just normal function calls, calling a function, it returns, calling the next function. That they start some asynchronous actions or return promise objects (which seem to get thrown away in your example) is irrelevant. – Bergi Nov 18 '19 at 19:38

1 Answers1

0

"On some computers, they may be executed in parallel, or in some sense concurrently, while on others they may be executed serially."

That MDN description is rubbish. I'll remove it. Promise.all is not responsible for executing anything, and promises cannot be "executed" anyway. All it does is to wait for its arguments, and you're the one who are creating those promises in the array. They would run (and concurrently open multiple connections) even if you omitted Promise.all and simply called notASingleton() multiple times.

the execution context for all of them will be dbConnected = false

Yes, but only because your dbConnected = true; is in a timeout and you are calling notASingleton() again before that happens. Not because Promise.all does anything special.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Instead of `setTimeout`, it could be any synchronous operation which takes time to be processed e.g. I have `Buffer.from(certificate, 'base64')` in my code before making the connection. The problem I think is that as long as the code inside each promise is executed in parallel, the issue will persist? – Houman Kamali Nov 18 '19 at 18:07
  • @HoumanKamali no code in JS is ever executed in parallel. But yes, as soon as you are doing something asynchronous, multiple asynchronous operations might be active at the same time. – Bergi Nov 18 '19 at 19:37