Static/dynamic way to detect dangling promises - javascript

I know this problem has been dealt with so many times, but none of them seem to solve the issue of reliably detecting dangling promises (even those that resolve properly).
So I want to be able to figure out a way (whether at runtime or better at static time) to root out "dangling promises", especially of this class:
async function a() {
... do some async operation ...
}
async function b() {
a(); // forgot to call await on it
}
where I have accidentally forget to await on one function and some task that executes asynchronously doesn't get awaited on. Often times these types of bugs don't throw exceptions so I can't just use "unhandledRejection" and call it a day.
At this point after lots of desperate attempts I just need a way to detect this kinda faulty pattern at either (optimally) static/compile/lint time or at runtime. Runtime for me I think should work assuming I have good test coverage.
tl;dr basically, I'm searching for some code that would do something like the following:
Error: Potential dangling promise detected!
...
at b (/dangling.js:5:3)
For each dangling promise
My thought process
I first tried finding some static analysis library that would help to detect these things (theoretically it should be possible but I had no luck with finding such a thing). A while back I remember finding something in the depths of stackoverflow that talked about using some typescript checker to check for dangling promises, though now I can't find it anymore :( . Though at the time changing the entire codebase to typescript was a no-go. (Later I learned you can actually use tsc to typecheck javascript (given that you do some type annotation in comments))
Currently I was previously using node version 11, so I thought about using the node.js's async_hooks API and try to listen on events (apparently simply monkey patching the Promise constructor wouldn't work because node.js bypasses the Promise constructor when creating a Promise object to return from an async function). Using node v11, after a bit of code hackery here it seemed to work (though it's not very efficient cause it throws away a lot of promise optimization in the v8 engine, but it does the job). There was a small hiccup in this entire operation in that I had to still monkey patch the then/catch/finally functions of the Promise API to check if we are currently calling that function (somehow that worked with detecting some dangling promises).
Now enter node v12 (apparently I need this for certain other things that are breaking), and now that hackery (unsurprisingly) completely breaks. After scrutinizing the version diffs, seems like they optimized the await/async implementation. After narrowing down the reason, it seems like await no longer calls the then function of the promise and just directly does some native shenanigans (idk what really).
Now I'm actually kinda desperate for some solution (and maybe if Typescript has some way of type-checking these dangling promises, what was the option to enable this check? I know (tested) that tsc doesn't do this by default).

You are most likely looking for the no-floating-promises rule of typescript-eslint

Related

Leaving #typescript-eslint/require-await on, but exempting individual instances?

I have a project where there have recently been a slew of recent bugs where async functions are not being awaited, resulting in some test infrastructure timeouts. We use typescript-esling, and I'd love to be able to enable the flag #typescript-eslint/require-await as an error, but there are times that I want exceptions: intentionally unawaited async calls.
Is there JS/TS syntax I can use to indicate to the linter that a given line has been intentionally not awaited? I'm hoping there's something similar to how discards will avoid the "variable not used" rule. Thanks!
Put a void in front of the unawaited promise. (potentially needs another rule to allow this, depending on your presets)

How to deal with dangling promises

Oftentimes I want to call a promise and just let it run asynchronously instead of waiting for it. Like:
... some code ...
fetchMyCount().then(count => {
updateTheUI(count);
});
... more code ...
Now this works great, but oftentimes I don't put a failure handler on the promise because that is pretty onerous for each one. That would be like putting a try {} catch {} on every one of my data fetches.
The problem is, if it fails, it does so silently. It doesn't throw an exception that my window.onerror can get, it doesn't log, it doesn't do anything.
Now I can make a nice wrapper like:
Async(fetchMycount().then(count => {
updateTheUI(count);
}));
which can generically deal with promises, but then I have to remember to wrap each and every async call.
Is there a way to have a top-level handler like window.onerror or a big try {} catch {} wrapper that can catch all un-dealt-with rejected promises so I can log them and make sure they get fixed?
This is a common use case. Your problem is real. In Node, you have process.on("unhandledRejection", ... events which you can use. Sadly, these are not available in browsers yet. Some libraries which are compatible with ES2015 promises offer them and you can use those until native promises get support.
The upside is that support in browsers is coming and browsers will eventually support these hooks.
Currently, I've convinced the polyfill to add it, so if you're using the core-js polyfill you can do:
window.onunhandledrejection = function(e){
// handle here e.promise e.reason
};
Note that this assumes that something is an unhandled rejection if: It's unhandled, and an error handler was not attached synchronously (within a task to be precise).
Exactly for issues like this, I wrote my own utility for writing asynchronous code:
https://github.com/vacuumlabs/yacol/
It does not help you with your existing code, however with newly written code it provides (among many other things) quite advanced error-handling capabilities.
This said, I don't think that "unhandledRejection" / "uncaughtException" events are good (although it's the best node.js natively provides) tools to do proper error handling:
First, you can only attach it process-wide, so it forces you to handle all uncaught errors the same way (unlike with try-catch where you can react differently to errors that originate from different parts of your code)
Second, "unhandledRejection" is called if .catch is not attached to the Promise in the same run of event loop. This may result in false errors being caught; note it is pretty OK to attach error handler to the promise later!

Resolving promises in Protractor and Cucumber using Chai as Promised

Lately a colleague and I have had some disagreements on the "right" way to implement Cucumber step definitions using Protractor and Chai as Promised. Our contention comes from a mutual lack of understanding of precisely what is going with promise resolution in the context of Cucumber.
We're testing against an AngularJS application, so resolving promises and asynchronous behavior is a necessary evil. The biggest problem we've had is forcing synchronous test behavior and getting Cucumber to wait on promises between step definitions. In some cases, we've observed situations such that Cucumber seems to plow straight through step definitions before Webdriver even executes them. Our solutions to this problem vary...
Consider the hypothetical scenario:
Scenario: When a user logs in, they should see search form
Given a user exists in the system
When the user logs into the application
Then the search form should be displayed
Most of the confusion originates in the Then step. In this example the definition should assert that all fields for the search form exist on the page, meaning multiple isPresent() checks.
From the documentation and examples I was able to find, I felt the assertion should look something like this:
this.Then(/the search form should be displayed/, function(next) {
expect(element(by.model('searchTerms')).isPresent()).to.eventually.be.true;
expect(element(by.model('optionsBox')).isPresent()).to.eventually.be.true;
expect(element(by.button('Run Search')).isPresent()).to.eventually.be.true.and.notify(next);
});
However, my colleague contends that in order to satisfy promise resolution, you need to chain your expects with then() like this:
this.Then(/the search form should be displayed/, function(next) {
element(by.model('searchTerms')).isPresent().then(function(result) {
expect(result).to.be.true;
}).then(function() {
element(by.model('optionsBox')).isPresent().then(function(result) {
expect(result).to.be.true;
}).then(function() {
element(by.button('Run Search')).isPresent().then(function(result) {
expect(result).to.be.true;
next;
});
});
});
});
The latter feels really wrong to me, but I don't really know if the former is correct either. The way I understand eventually() is that it works similarly to then(), in that it waits for the promise to resolve before moving on. I would expect the former example to wait on each expect() call in order, and then call next() through notify() in the final expect() to signal to cucumber to move on to the next step.
To add even more confusion, I've observed other colleagues write their expects like this:
expect(some_element).to.eventually.be.true.then(function() {
expect(some_other_element).to.eventually.be.true.then(function() {
expect(third_element).to.eventually.be.true.then(function() {
next();
});
});
});
So the questions I think I'm alluding to are:
Is any of the above even kinda right?
What does eventually() really do? Does it force synchronous behavior like then()?
What does and.notify(next) really do? Is it different from calling next() inside of a then()?
Is there a best practices guide out there that we haven't found yet that gives more clarity on any of this?
Many thanks in advance.
Your feeling was correct, your colleague was wrong (though it was a reasonable mistake!). Protractor automatically waits for one WebDriver command to resolve before running a second. So in your second code block, element(by.button('Run Search')).isPresent() will not resolve until both element(by.model('optionsBox')).isPresent() and element(by.model('searchTerms')).isPresent() are done.
eventually resolves promises. An explanation is here: https://stackoverflow.com/a/30790425/1432449
I do not believe it's different from putting next() inside of then()
I do not believe there is a best practices guide. Cucumber is not a core focus of the Protractor team, and support for it is largely provided by the community on github. If you or someone you know would like to write a best practices guide, we (the protractor team) would welcome a PR!
What works for me is this - The function below searches for something that will always equal true - in the case the existence of a html tag. I call this function at the end of each test, passing in the callback
function callbackWhenDone(callback) {
browser.wait(EC.presenceOf(element(by.css('html'))))
.then(function () {callback();})
}
This is the usage in a simple test:
this.Given(/^I go on "([^"]*)"$/, function (arg1, callback) {
browser.get('/' + arg1);
callbackWhenDone(callback);
});
A bit of a hack I know but it gets the job done and looks pretty clean when used everywhere

How do I work with the Bluebird error handler?

Introduction
This question aims to, eventually, resolve a problem I'm having in development with Bluebird. However, I'm also using the opportunity to get some things clarified, so there will be side-questions. I also apologize in advance for any feelings of confusion or boredom you might experience while reading the story to come.
Questions
As far as my understanding goes, Bluebird attempts to intelligently catch ignored rejections, according to the following strategy:
The second approach, which is what bluebird by default takes, is to call a registered handler if a rejection is unhandled by the start of a second turn.
-- Bluebird Readme # Error Handling
Now herein lies the first side-question: What does "the start of a second turn" mean?
Later in the same section, the following is documented:
Of course this is not perfect, if your code for some reason needs to swoop in and attach error handler to some promise after the promise has been hanging around a while then you will see annoying messages. In that case you can use the .done() method to signal that any hanging exceptions should be thrown.
-- Bluebird Readme # Error Handling
Now, I believe that I ran in to the situation described above with my use case being as follows:
I call a function which will provide me the promise to which I attach a .catch():
lib.loadUrls()
.catch(function(e){console.log(e);});
Internally, that function loads content from URL1 and based on the content, loads content from URL2 in sequence:
lib.loadUrls =
return this.loadUrl1()
.then(this.loadUrl2.bind(this))
If the second promise in this chain is rejected the error is handled by the catch first, and then by Bluebirds Possibly unhandled error handler as well.
This last behavior is unwanted and I can not figure out why it's doing this. So question two could be: Why, despite an error handler being attached and executed, does Bluebird still consider the possibility of the error being "unhandled"?
I'm thinking, apparently the promise "has been hanging around for a while" by the time the rejection propagates to the .catch(). In which case I should solve it (according to the quoted documentation) by "using the .done()".
Now, I've tried several things, but I can't quite figure out how to "use the .done" in this scenario. (It doesn't help that .done() returns undefined, preventing me from .finally-ing.)
So this introduces my third, and fourth questions: How do I use .done() in this scenario, and how do I explicitly conclude a promise-chain, but still attach a .finally()
EDIT 1: I've created some JSFiddles to reproduce the bug:
Using Bluebird 1.0.5 reproduces the bug.
Using the latest Bluebird.js reproduces the bug (at this time)
Using Beta version 0.10.0-1 does not reproduce the bug.
EDIT 2: The dev fixed the bug.
This was indeed just a regression bug in bluebird and is now fixed.
The bit about needing to use .done() is pretty much theoretical, you won't run in a situation in practice where you would need to attach error handlers in such a way that would cause false positives to be reported.
It's most likely Bluebird bug, as handled error should not be reported (assuming you properly handle promises in loadUrls body). So probably you should report it to Bluebird issue tracker.
Concerning done, it's pure access function that's best if used instead of then or catch when you just process resolved value.
It's good to treat done as first choice function and use then and catch only if you really need transformation to other promise, with such approach you also don't need to rely on buggy errors monitoring (best to turn it off completely).
In your case done should be used as:
lib.loadUrls().done(); // eventual error will be thrown
and if for some reason you want to handle error specifically (e.g. in running server you don't want it throw) do:
lib.loadUrls().done(null, function (error) {
// handle error
});
EDIT:
Just noticed, that you still want to process promise returned by lib.loadUrls().catch(..) with finally. In such case done is not a solution. done should only be used as final call, but you can combine it with finally as follows:
lib.loadUrls().finally(function () {
// cleanup
}).done();

How do I debug promise-based code in node?

I am using Cujo's great When library to provide a Promises/A+ implementation for my Node project, although this question is not node-specific.
Generally, When's great: it lets me write more maintainable, readable code.
However, when my callbacks fail unexpectedly (accessing a property of a null variable, etc), the exceptions are effectively swallowed by When, as seems to be specified by the Promises/A+ spec. Unfortunately, this means I don't get any feedback about the error (other than the callback stops executing at that point). No error type or message, no line number.
To illustrate:
// hypothetical asynchronous database query
database.query(queryDetails).then(function(result) {
var silly = 3.141592654;
silly(); // TypeError: number is not a function!
process(result); // this code is silently never executed
});
I can think of a handful of (unacceptable) ways to tackle this:
providing failure callbacks for every then call (to dump the reason/exception to the console)
wrapping all callback bodies in try-catches
littering the codebase with "landmark logs" ala console.log('I got here 123')
Am I just doing it wrong? Surely I'm not alone in finding the debuggability of promises-based code poor. Is there an obvious solution I'm missing?
Update Sep 2016: NodeJS 6.6.0+ and 7.0+ will warn automatically on unhandled rejections. Run node with --trace-warnings to get reasonable stack traces. Still not as good as what bluebird gives you but a lot better than the situation before.
Ok, so summing up the information from the comments and add some.
There is nothing in the Promises/A+ specification that dictates how to deal with this problem. The specification is about the minimal requirements for good interop between different promise libraries - so one promise library can consume promises created in another and vice versa.
Several libraries solve this problem by including a .done method that explicitly declares the chain has ended, this causes uncaught rejections to be thrown. Libraries like When and Q solve the problem this way. For example if your .then after .query was a .done you'd get a long stack trace.
Newer, less naive implementations of promises like Bluebird solve this problem by automatically figuring out possibly uncaught rejections and logging them out for you loudly. They also give you a hook. When has experimental support for this using the monitor.
As such:
require('when/monitor/console'); // when will now log async rejections used with
// `then` , this is experimental in when.
With bluebird
Promise.longStackTraces(); // Bluebird always logs async rejections but with this
// option it will stitch the asynchronous context stack
// for you in your methods.
ES6 promises' behavior is unspecified on this. There is no explicit requirements on the native implementations with regards to this. However, I know vendors are working on it and I'd expect engines to start figuring this out even in native implementations.
Here is how I detect when a Promise has been rejected on Node but not caught:
if (typeof process === 'object') {
process.on('unhandledRejection', (error, promise) => {
console.error("== Node detected an unhandled rejection! ==");
console.error(error.stack);
});
}
In addition to that, you could use this monkey wrapper to provide long stack traces for Node's ES6 Promises. It produces similar output to Q's longStackSupport. I would not recommend it for use outside of development code, due to performance concerns. (It's working for me in Node v4.4.1. I have not yet tested it in Chrome or Firefox.)

Categories