5

I want to call a given function asynchronously. The wrapper function tryCallAsync is one way of doing this. This approach works. However, it requires that the callback for setImmediate to be an async function. This seems wrong, as the callback is returning a Promise that is not used. Is it wrong to pass an async function to setImmediate for this purpose?

async function tryCallAsync(fn, ...args) {

    return new Promise((r, j) => {

        setImmediate(async () => {

            try {
                r(await fn(...args));
            }
            catch (e) {
                j(e);
            }
        })
    })
}

//  Using tryCallAsync

let resolveAsync = tryCallAsync(()=>{
    return new Promise((r,j)=>{
        setImmediate(()=>r('resolveAsync'));
    });
})

resolveAsync.then((resolve)=>console.log(resolve));

let resolve = tryCallAsync(()=>{
    return 'resolve';
});

resolve.then((resolve)=>console.log(resolve));

NB: https://www.youtube.com/watch?v=e3Nh350b6S4

stackhatter
  • 304
  • 2
  • 11

2 Answers2

6

Yes, it's wrong, for multiple reasons:

  • setImmediate doesn't handle the returned promise, especially it doesn't deal with errors1
  • Don't put business logic in asynchronous (non-promise) callbacks when using promises. Settle a promise from there, nothing else.

1: And even while your particular callback never rejects the returned promise due to the try/catch, it still feels wrong

Your function should be written as

async function tryCallAsync(fn, ...args) {
    await new Promise(resolve => {
        setImmediate(resolve);
    });
    return fn(...args);
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • This is the best approach I've seen. You wrote, "Don't put business logic in asynchronous (non-promise) callbacks when using promises. Settle a promise from there, nothing else." I've read that several times and I cannot figure out what you meant. Can you elaborate on your meaning? What specifically are you referring to as "business logic"? – stackhatter Jan 01 '21 at 17:22
  • In a callback-based API, you need to put the code (business logic) that should run after the asynchronous action is completed inside the callback. In a promise-based API, you put the code in a `.then()` callback or after an `await`. When you want to write promise-based code, you need to [promisify the callback APIs](https://stackoverflow.com/q/22519784/1048572), and should do that by placing nothing but `if (err) reject(err) else resolve(result)` in the callback. – Bergi Jan 01 '21 at 17:29
1

This approach doesn't waste a Promise, however, still, it's not as performant as the conventional way of doing this.

function tryCallAsync(fn, ...args) {

    return new Promise((r, j) => {

        setImmediate(() => {

            (async function () {

                return await fn(...args);

            })().then(r).catch(j);
        });
    });
}
stackhatter
  • 304
  • 2
  • 11
  • Not sure what you mean by "waste a promise", this approach actually creates even more promises than the original :-) You could simplify it to `return new Promise(resolve => { setImmediate(() => { resolve((async () => fn(...args))()); }); });` – Bergi Jan 01 '21 at 19:27
  • @Bergi I was just referring to the async function that I had passed to the setImmediate. Thank you for sharing that code! I was perplexed at how it handles errors, but I think I see it now. It gives me a lot to think about :-) – stackhatter Jan 02 '21 at 13:36