1

Is there any typescript config option or some workaround to check if there is no resolve called in new Promise callback?

Assume I have a promise

new Promise(async (resolve, reject) => {
    try {
        // do some stuff
        // but not calling resolve()
    } catch (e) {
        reject(e)
    }
})

I want typescript to warn me that I did not call resolve(). Is it possible?

I know that I can use noUnusedParameters, but there are a couple of situations where I still need unused parameters (e.g. request inside of express.Hanlder, where I only use response, etc.)

Lux
  • 17,835
  • 5
  • 43
  • 73
Limbo
  • 2,123
  • 21
  • 41
  • I assume you want compile-time checks and no run-time hacks. Correct? – k0pernikus Jan 22 '20 at 12:13
  • @k0pernikus actually yes, but if there is no compile-time solution, run-time solution will be also helpful – Limbo Jan 22 '20 at 12:27
  • 3
    You'll be happier if you can put most of your logic into an `async` function and call that. If you forget to return a value from an async function (the equivalent of forgetting to call `resolve`) then it will show up in TypeScript's inferred return type (it will be `Promise` instead of `Promise`). – danvk Jan 22 '20 at 12:39
  • Actually every situation when it is possible to check that is a situation where `new Promise(` is not the right solution! – Lux Jan 22 '20 at 12:44
  • @danvk yes, you're right, but `async / await` does not provide as powerful error handling as it is inside the promise callback [(see examples section)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) - and this is true, I actually went to `return new Promise(...)` from `async / await` after couple of `unhandledRejection`s – Limbo Jan 22 '20 at 12:52
  • @Lux cannot agree with you. See my comment about error handling above – Limbo Jan 22 '20 at 12:54
  • 1
    @Limbo What do you mean with async / await not having as powerful error handling? You can just throw within an async function and it will be turned into a rejection for you. – k0pernikus Jan 22 '20 at 13:03
  • @k0pernikus any rejected `await` inside of `async` function will not `throw`, but just reject that will cause `unhandledRejection`. Catching it through `try / catch` and throwing it inside `catch` block is actually weird and also it will cause to lost extra information from rejection – Limbo Jan 22 '20 at 13:11
  • 1
    Looks like a different question. Do you maybe want to ask it? Because you're certainly doing something wrong. So just ask a new question how you can avoid the `unhandledRejection` with some code and what you've tried. I'm sure we'll figure it out. – Lux Jan 22 '20 at 13:13
  • @Lux well, the question is clear, I think. If there is no solution - ok then, I will just add linter warning on unused variables. – Limbo Jan 22 '20 at 13:15
  • 1
    I just want to help. But I can guarantee you: `new Promise(async` is *always* a bad idea. Have a look [here](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it). So your question is a X/Y Problem. – Lux Jan 22 '20 at 13:16
  • 1
    you can only write custom eslint rule for this – Orkhan Alikhanov Jan 22 '20 at 13:25
  • @OrkhanAlikhanov yeah, considering to do it, thank you :) – Limbo Jan 22 '20 at 13:26
  • For anybody "antipattern" mates - read [this](https://medium.com/@JonasJancarik/handling-those-unhandled-promise-rejections-when-using-javascript-async-await-and-ifee-5bac52a0b29f) and you probably will understand me (but I actually don't hope so) – Limbo Jan 22 '20 at 13:28

2 Answers2

2

No, that is not possible. Knowing whether code calls a certain function (resolve in this case) is just as hard as the halting problem. There is proof that no algorithm exists that can always determine this.

To illustrate, let's assume that the algorithm for determining whether a function calls resolve exists, and is made available via the function callsResolve(func). So callsResolve(func) will return true when it determines that func will call resolve (without actually running func), and false when it determines that func will not call resolve.

Now image this func:

function func() {
    if (!callsResolve(func)) resolve();
}

... now we have a paradox: whatever this call of callsResolve returned, it was wrong. So for instance, if the implementation of callsResolve would have simulated an execution of func (synchronously) and determines that after a predefined timeout it should return false, the above is a demonstration of a function that calls resolve just after that timeout expired.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Yes, I understand it. I meant I want to be warned if there is **no one** resolve called (like if `resolve` still unused), but for now I understand that the best solution will be to add an extra rule to my linter that will highlight unused parameters) Thank you for your answer – Limbo Jan 22 '20 at 14:06
  • 1
    Yes, but also imagine `if (false) resolve();`. That effectively references `resolve`, but it will never execute. And that is just the most simple case. This can be more complex, so that you cannot really see whether `resolve` will be called or not. – trincot Jan 22 '20 at 14:57
1

The closest you can get to a compile time check is to use async / await syntax.

If you don't want to use that, you could timeout your promises, though you would have to do that with each of your promise after / when you are creating them.

A solution could look like this:

export const resolveAfterDelay = (timeout: number) => new Promise((r) => setTimeout(r, timeout));

export const rejectAfterDelay = async (timeout: number) => {

    return new Promise((resolve, reject) => setTimeout(() => reject(`Promise timed out as resolve was not called within ${timeout}ms`), timeout));

};

export const timeoutPromise = <T>(timeout: number) => async (p: Promise<T>): Promise<T> => {
    return Promise.race([p, rejectAfterDelay(timeout)]);
};

const timeoutAfter1s = timeoutPromise(1e3);
const timeoutAfter10s = timeoutPromise(10e3);

timeoutAfter10s(resolveAfterDelay(3e3)).then(success => console.log("SUCCESS IS PRINTED")).catch(console.error); // works
timeoutAfter1s(resolveAfterDelay(3e3)).then(success => console.log("NEVER REACHED")).catch(console.error); // aborts

const neverResolvingPromise = new Promise(() => {
});

timeoutAfter1s(neverResolvingPromise).catch(console.error); // promise never resolves but it will be rejected by the timeout

It makes use of Promise.race. Basically, whatever first resoves or rejects will be returned. We want to always reject a Promise if it does not resolve in time.

You would always have to wrap your Promise on creation like

timeoutAfter10s(new Promise(...));

And you would have to adapt the timeout according to your use case.

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
  • Sounds good despite it is a runtime solution. Also it possibly could help to find weak (in meaning of execution time) places of code. Thank you – Limbo Jan 22 '20 at 13:43