1

I have a small npm package of utilities Utilities that I want to use across two of my codebases, AppA and AppB.

Utilities will expose a class with some functions, and it is AppA and AppB's job to subclass where needed.

class Utilities {

  processData(data, callback)  {
    var result = this.validateData(data);
    // This will fail for AppB, because validateData returns a promise.
    var length = result.length;
    callback(length);
  }

  validateData(data) {
    // Must be subclassed
  }

}

AppA:

class AppA extends Utilities {
  validateData(data) {
    return [1, 2, 3];
  }
}

AppB:

class AppB extends Utilities {
  validateData(data) {
    // returns a Promise
    return AsyncMethods.checkData(data);
  }
}

The Utilities package is written in a synchronous manner, as is app A. AppB however requires interfacing with some async methods inside of validateData. My Utilities class however does not know that the consumer has an async API.

I can change both Utilities and AppB with ease, but not AppA. So I need a way to write Utilities in a way that can work in both sync and async environments.

Ideally, I'd just modify AppB and make it wait:

class AppB {
  async validateData(data) {
    var result = await AsyncMethods.checkData(data);
    return result;
  }
}

However, this still returns a Promise object and not the actual result, because it's an async function.

Another solution that falls just short of working is waiting inside processData in Utilities:

  async processData(data, callback)  {
    var result = await this.validateData(data);
    var length = result.length;
    callback(length);
  }

However, this breaks AppA, as its implementation is not async, and I cannot change AppA.

Do I have any options here? Perhaps a way to make all functions await by default?

Here's a fiddle: https://jsfiddle.net/1ptoczh7/3/

Notice for AppB the result is undefined. I need to be able to get both outputs without changing AppA.

Snowman
  • 31,411
  • 46
  • 180
  • 303
  • _However, this breaks AppA, as its implementation is not async, and I cannot change AppA_ Your premise is flawed. You can `await` anything. If it doesn't implement `.then()`, it will be implicitly wrapped with `Promise.resolve()` and then `await`ed for a tick, regardless of whether it's synchronous or not. I'd say just make `processData()` async and be done with it. – Patrick Roberts May 21 '18 at 21:00
  • What @Patrick said... as long as AppA doesn't have to wait for `processData()` to complete. [fiddle](https://jsfiddle.net/0dskfqx6/1/) –  May 21 '18 at 21:15
  • You simply cannot use an asynchronous API and return a synchronous result. It simply cannot be done in Javascript. You will have to either make two versions of your utilities, one synchronous and the other asynchronous or you will have to make the one version be asynchronous so that you can use async operations in it. Said another way, async is poisonous. Once anything is async in the API, the entire result is forced to be async and you can never convert it back to synchronous. – jfriend00 May 21 '18 at 23:46
  • Thanks, ended up making the library fully asynchronous. Best design route I think. – Snowman May 22 '18 at 02:31

1 Answers1

0

I need a way to write Utilities in a way that can work in both sync and async environments.

Write two different utilities. You will almost surely end up with that in any case. Factor out common code into a shared module (have a look at this answer for how to effectively do that). Asynchronous functionality is just too different from synchronous functionality.

In your example, this would most likely look like this:

function getLength(result) {
  return result.length;
}

class Utilities {
  processData(data) { // returns int
    return getLength(this.validateData(data));
  }
  // abstract validateData(data): Result
  // to be overwritten in subclass
}
class AsyncUtilities {
  processData(data) { // returns Promise<int>
    return this.validateData(data).then(getLength);
  }
  // abstract validateData(data): Promise<Result>
  // to be overwritten in subclass
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thanks, that's good advice. I ended up just making the whole library asynchronous. The changes in both AppA and AppB weren't too bad as a result, and it seems like the better design choice. – Snowman May 22 '18 at 02:31