tl;dr - how to make a version of a class that has all its async methods replace by sync ones? (or visa versa)
Background info:
I have created an abstract base class using symbols, something like this:
const sProtectedMethod = Symbol();
class AbstractBase() {
constructor() {
if (new.target === AbstractBase) {
throw new TypeError(`AbstractBase is abstract and cannot be instantiated directly. Use a derived class`);
}
}
async #protectedMethod() { // this is just a convience to avoid the ugly symbol syntax everytime we call the function
await this[sProtectedMethod]();
}
async publicMethod() {
await longAndComplexFunctionCallChainThatEventuallyAwaitsProtectedMethodButDoesntAwaitAnythingElse();
}
where the derived classes look like this:
class Derived extends AbstractBase {
async [sProtectedMethod]() {
// implementation here
}
So far so good. But - in some derived classes the implementation of the overriden method must be async (because it does I/O or whatever) while in other implementations this method could be synchronus. I did an experiment to see if making the method synchronus would be faster and it is - approx. 1000x faster.
I could make 2 versions of the AbstractBase - a sync and an async version. But here's the catch - the protectedMethod is actually called many times, pretty deep into a long chain of functions - all of which must be async if the overriden implementation is also async.
So rather than maintain 2 versions of the class that are almost identical, I'd like a way to automatically create a sync version from an async version, or visa versa.
I have managed this with a string replace and an eval
const AbstractBaseSync = eval(`${AbstractBase.toString().replace(/async|await/g, ``)}; AbstractBase`);
But even though I think this is safe (providing there are no strings within the class that contain the words async or await), the use of eval and I'm hoping there is a better way.
Also, although it seems to work in Chrome, Firefox + Node - I know implementations of toString may differ, which might cause issues later.
If I dispensed with the #private members and used _convention instead, then maybe I could do it member by member with reflection, but I don't think it would be much better as eval or new Function(...) would still be required.
What is the cleanest way to do this?