5

How do you handle promises that do not resolve?

Example:

class Utils {
    static async thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(number: number) {
        return new Promise((resolve, reject) => {
            if(number === 2) {
                resolve('ok')
            }
        })
    }
}

console.log(await Utils.thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(2))
// this will print "ok" because 2 is passed and the promise is resolved

console.log(await Utils.thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(5))
// this will crash the program silently 

uncaughtException and unhandledRejection return nothing when the promise is unresolved. Adding a try/catch around the await doesn't work (no errors). Finally, the only thing that works is using Promise.then instead of await.

Problem is the code base is riddled with async/await and Promises that sometimes resolve (depending on conditions)

Question: Is there a typescript flag I can add to detect a missing resolve/reject? or maybe an automated way to transpile all the async/await to use Promise.then?

When using a debugger, the program stops after the Promise and it is difficult to find which function/promise has a missing resolve/reject.

Rewriting all the async/await calls to use Promise.then is my last resort.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
hbt
  • 1,011
  • 3
  • 16
  • 28
  • 1
    *Problem is: the code base is riddled with async/await and Promises that sometimes resolve* This is a problem to fix with the codebase. While there are ways around it (eg, await a Promise.race of both that Promise and a Promise that rejects after 60 seconds), you should *not* do that in the consumer - fix the functions that give you those Promises instead, they're essentially broken – CertainPerformance Jan 03 '20 at 05:22
  • return new Promise((resolve, reject) => { if(number === 2) { resolve('ok') }else{ resolve('not ok') } }) – Mahesh Bhatnagar Jan 03 '20 at 05:23
  • @CertainPerformance Do you know how I could get a stacktrace or error when this happens?? What pisses me off is this is happening silently; our error logging is empty because node reports nothing. I'm thinking of wrapping the entire program and return an exit code in order to detect it. As mentioned above, the code base is littered with it and I need to size up the problem before refactoring all these promises. Thanks! – hbt Jan 03 '20 at 05:36
  • 2
    For the general case, it's not possible to *know* from the outside whether the Promise is going to hang forever or if it's eventually going to resolve (or reject). Fixing the base code so that it isn't fundamentally broken should be the first step, not the second – CertainPerformance Jan 03 '20 at 05:37

2 Answers2

10

If you have promises that occasionally don't resolve or reject and that's not the way they are supposed to work (which it usually isn't), then you just have to fix that. There really is no work-around. The proper fix is to get down to the lowest level and fix the code so it reliably resolves or rejects every time.


This is not the proper fix, but implementing a timeout wrapper could help with debugging giving you a log message with some semblance of a stack trace for a timed out promise:

function rejectT(t) {
    // create potential error here for better opportunity at stack trace
    let e = new Error("Promise timed out");
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(e);
            reject(e);
        }, t);
    });
}

function timeout(p, t = 5000) {
    return Promise.race([p, rejectT(t)]);
}

You can then wrap any promise such that instead of:

fn().then(...).catch(...)

You can use:

timeout(fn()).then(...).catch(...);

Or, if you want to set a custom timeout value:

timeout(fn(), 1000).then(...).catch(...);

Again, this is debugging code to help find the culprits that need fixing and to help test fixes, not to bandaid your code.

Rewriting all the async/await calls to use Promise.then is my last resort.

I don't see how this is going to help at all. If await never finishes, neither will promise.then(). They are exactly the same in that regard. If the promise never resolves or rejects, then the .then() handler will never get called either.

Problem is the code base is riddled with async/await and Promises that sometimes resolve (depending on conditions)

There's no shortcut here other than methodical code review to find suspect code that has code paths that may never resolve or reject and then building unit tests to test every function that returns a promise in a variety of conditions.

One likely source of code that never resolves or rejects are some of the promise anti-patterns. The precise reason some of them are anti-patterns is because they can be very easy to mess up. Here are a few references that might spike your sensitivity to suspect code:

Promise Anti-Patterns

Common Promise Anti-Patterns and How to Avoid Them

ES6 Promises: Patterns and Anti-Patterns

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I already use tslint and have the "promise" rules turned on; do you know if there is another linter capable of detecting these anti-patterns for me? So far, I'm thinking of logging the requests and some exit code; the ones without the exit code means the program terminated unexpectedly – hbt Jan 03 '20 at 06:00
  • 1
    @hbt - I do not know of a linter that detects promise anti-patterns. It would be more feasible with TypeScript where it is known that a function returns a promise. – jfriend00 Jan 03 '20 at 06:01
  • 2
    FYI: found a way to detect promises that don't resolve/reject using async hooks https://nodejs.org/api/async_hooks.html -- I can identify which requests produce unresolved promises then fix them. I used a codemod to add the hooks around await calls then ran the test suite and old requests that looked buggy to find which promises are broken. Thank you! – hbt Jan 03 '20 at 07:52
  • @hbt - Pretty cool. Something relatively new in node.js, I guess. – jfriend00 Jan 03 '20 at 07:55
0

async function thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(number) {
  return new Promise((resolve, reject) => {
    if (number === 2) {
      resolve('ok')
    } else {
      reject('error:' + number)
    }
  })
}
(async() => {
  try {
    console.log(await thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(2))
    // this will print "ok" because 2 is passed and the promise is resolved
  } catch (e) {
    console.error(e);
  }
  try {
    console.log(await thisFunctionOnlyResolvesWhenPassed2AndNeverRejects(5))
    // this will crash the program silently
  } catch (e) {
    console.error(e);
  }
})()
Xixiaxixi
  • 172
  • 10
  • 2
    This isn't really the question. The OP was just demonstrating a sample promise that is unreliable. The real problem is deep in some other code that the OP probably didn't write, but is trying to fix code that uses these unreliable promises. – jfriend00 Jan 03 '20 at 05:47