Ignition version 8.1.26
There seems to be a bug in the Promise objects used by system.net.httpClient()
async methods. The Promise's then()
method requires a callback. That callback can have either one argument or two.
The problem manifests itself when the callback code raises a TypeError.
Incorrect/Confusing error message
Consider the following code that has an intentional TypeError in the callback, which represents an unintentional TypeError.
promise=system.net.httpClient().getAsync("http://google.com/")
def callback(response,error):
return response['json'] # accidental type error
promise.then(callback).get()
This code raises what seems to be an incorrect error: java.util.concurrent.ExecutionException: java.util.concurrent.ExecutionException: TypeError: callback() takes exactly 2 arguments (1 given)
. What? There's something wrong with the callback's number of arguments? This is very confusing.
Callback called twice
I can adjust the callback to be callable with either one or two arguments by giving the error
argument a default value.
promise=system.net.httpClient().getAsync("http://google.com/")
def callback(response,error=None):
system.util.getLogger('httpclient_issue').info('callback called')
return response['json'] # accidental type error
promise.then(callback).get()
This time I get the correct error message TypeError: 'com.inductiveautomation.ignition.common.script.builtin.http.Response' object is unsubscriptable'
, but the callback is called twice. You can see that by checking the logs for the httpclient_issue
logger and see two entries of 'callback called'.
Root Cause and Possible Solutions
It seems that the Ignition code tries to call the callback with two arguments, and if that call fails with a TypeError, then the callback is called with one argument. For most cases that's fine. The problem is that it gives unexpected results when the callback throws a TypeError.
The solution isn't "Don't have any of your callbacks raise a TypeError". That'd be great, but the TypeError is, of course, unintentional. The solution would be one of these:
- Determine how many arguments the callback should be called with before attempting to call it. Then it wouldn't be necessary to try to call the callback twice if the first attempt had the wrong number of arguments.
- Keep the current behavior except only re-try the callback with a different number of arguments if you verify that the TypeError is caused by calling the callback with the wrong number of arguments. In the code earlier, the TypeError is caused by an object not being subscriptable, not an incorrect number of arguments.
Last Words
I understand that not many people get affected by this, so I don't expect this bug to be a high priority. It would be nice if it were fixed, but I write this down mostly for anyone else who's wondering why they're getting TypeError: callback() takes exactly 2 arguments (1 given)
when they shouldn't. It's because you have a TypeError in your callback and the Ignition code isn't handling it right.