I'm trying to do this in TypeScript, but I think the question is broader than TypeScript and applies to JavaScript as well, hence both tags.
I'm writing some generic algorithm functions (e.g. Nelder-Mead optimizer, ...) that take a user-supplied function as a parameter. I would like the algorithm to support both sync and async user functions. Is there a way to do that?
So for example, let's take the following over-simplified algorithm:
function findZero(f: (x: number) => number): number {
for(let i = 0; i < 1000; i++) {
const fi = f(i)
if(fi == 0) return i
}
return -1
}
console.log(findZero(x => 5-x)) // outputs: 5
console.log(findZero(x => new Promise<number>(resolve => resolve(5-x)))) // outputs: -1, since a Promise != 0
Now obviously, I can convert that into an async version very easily:
async function asyncFindZero(f: (x: number) => Promise<number>): Promise<number> {
for(let i = 0; i < 1000; i++) {
const fi = await f(i)
if(fi == 0) return i
}
return -1
}
console.log(asyncFindZero(x => 5-x)) // outputs: Promise { <pending> }
console.log(await asyncFindZero(x => 5-x)) // outputs: 5
console.log(await asyncFindZero(x => new Promise<number>(resolve => resolve(5-x)))) // outputs: 5
But I would like to avoid having two almost identical functions, with just a few await
s and Promise
s of difference.
Is there a way to either convert or (re)write the algorithm function such that it can both:
Something with a signature like:
function findZero<MaybeAsync extends number|Promise<number>>(
f: (x: number) => MaybeAsync,
): MaybeAsync {
// How to write this?
}
// Such that these both work:
console.log(findZero(x => 5-x)) // outputs: 5
console.log(await findZero(x => new Promise<number>(resolve => resolve(5-x)))) // outputs: 5
Alternatively, having two separate functions would also work, since I know statically which variant I need. Something like:
function masterFindZero(/*...*/) {/*...*/}
const syncFindZero = convertToSync(masterFindZero)
const asyncFindZero = convertToAsync(masterFindZero)
The actual algorithms are obviously more complicated, but I'm hoping that once I learn the missing concepts, I can generalise it myself.
You can write the master function as a generator function, then run it either synchronously or asynchronously:
function* findZero(): Generator<number, number, number> {
for (let i = 0; i < 1000; i++) {
const fi = yield i;
if (fi == 0) return i;
}
return -1;
}
function findZeroSync(f: (x: number) => number): number {
const gen = findZero();
let step = gen.next();
while (!step.done) {
step = gen.next(f(step.value));
}
return step.value;
}
async function findZeroAsync(f: (x: number) => Promise<number>): Promise<number> {
const gen = findZero();
let step = gen.next();
while (!step.done) {
step = gen.next(await f(step.value));
}
return step.value;
}