admin管理员组

文章数量:1287505

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 awaits and Promises of difference.

Is there a way to either convert or (re)write the algorithm function such that it can both:

  • take a synchronous user function as a parameter, and return a result
  • or take an async user function and return a Promise?

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.

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 awaits and Promises of difference.

Is there a way to either convert or (re)write the algorithm function such that it can both:

  • take a synchronous user function as a parameter, and return a result
  • or take an async user function and return a Promise?

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.

Share Improve this question edited Feb 23 at 17:35 Niobos asked Feb 23 at 16:48 NiobosNiobos 9304 silver badges15 bronze badges 5
  • This might be possible, but please edit your code to be a minimal reproducible example that demonstrates what you're doing with no distracting errors unrelated to your question. If I look at your stuff in an IDE I get errors completely unrelated to what you are asking about. They should be fixed ahead of time so we don't have to bother talking about them in an answer. – jcalz Commented Feb 23 at 17:09
  • 1 (see prev comment and please make the requested edit). Are you allowed to call f() once to see what it does? Otherwise it's not obvious how to determine that it is sync or async (you can't inspect a function in JS for that) – jcalz Commented Feb 23 at 17:11
  • I've edited to reduce the number of errors. However, some errors/warnings remain, but these are exactly the point of my question. The algorithm will call f() repeatedly, so adding one additional call would be acceptable. – Niobos Commented Feb 23 at 17:27
  • Shouldn't findZero be async if it should support both sync and async functions? – Anatoly Commented Feb 23 at 17:35
  • 1 I can make an async findZero that accepts both sync & async functions, but I would like to have a sync findZero for the sync case – Niobos Commented Feb 23 at 17:36
Add a comment  | 

1 Answer 1

Reset to default 1

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;
}

本文标签: