How To Use try, catch, throw And assert()
Squirrel provides programmers with a mechanism for intercepting potentially fatal errors engendered by their code before they cause an imp or its agent to crash. On the Electric Imp Platform, such crashes prompt the device to be restarted; the code is run afresh. Because this may lead inevitably to the same conditions which prompted the error in the first place, these errors are best dealt with by examining and altering the code. However, the error may have arisen as a result of transient factors: an unexpected value being sampled from a peripheral device, for instance. Squirrel’s error handling system allows you to deal with these irregular errors without forcing the imp or its agent to restart.
Squirrel’s error handling mechanism is a version of the standard try... catch
structure, which defines two code blocks: one containing the code you want to attempt to run and a second to be called if the first block generates an error, technically dubbed an ‘exception’. For example:
local a = {};
try {
// Do something that generates an error: mis-assign a new table slot
// Should be a.newSlot <- 1234
a.newSlot = 1234;
} catch(exception) {
server.error(exception);
// Displays "ERROR: the index 'newslot' does not exist" in the log
}
The exception generated by the try code block is a string containing an error message, and this string is passed to the catch block. The keyword catch
must always followed by a single parameter representing the exception, here stored in a variable called exception. The string can be parsed to allow your code to adapt its response to one of a number of possible runtime errors.
Variables declared to be local to the try
block are not accessible in the catch
block, but variables declared in the broader context are. Consequently, the catch
code can be used to modify variables involved in the error:
local a = {};
try {
// Do something that generates an error: mis-assign a new table slot
a.newSlot = 1234; // Should be a.newSlot <- 1234
} catch(exception) {
server.error("Exception generated... fixing")
// Displays "ERROR: Exception generated... fixing" in the log
a.newSlot <- 1234;
}
Squirrel continues executing any code following the try... catch
structure once either code block completes, so in the above example, we might add a final line to confirm the error has been suitably corrected:
server.log(a.newSlot);
This displays 1234
in the log.
Errors generated by code outside the try... catch
will be handled by Squirrel in the usual way. However, it is permissible to nest try... catch
structures.
If an error is not generated by the code in the try
block, it’s nonetheless possible to call the catch
code. To do so, use Squirrel’s throw
command within the try
block. It takes a single value of any kind:
throw "A serious error has occurred";
The value is passed into the catch
block as the latter’s single parameter.
You can also include throw
inside the catch
code too, in order to relay the exception on to the Squirrel runtime engine — or to an outer level of try... catch
if your code contains one or more of these structures in a nest formation.
try {
// Task that may fail...
} catch(error) {
throw error;
}
This allows you to perform tasks before Squirrel processes the error in the usual way. If your code disables WiFi, for instance, you might wish to re-enable it in the catch code and connect to the Electric Imp server so that an exception can be logged.
You can include throw
anywhere else in your code too. Doing so will trigger Squirrel’s own error handler; the passed value will be logged along with the line number of the throw
statement as if it were one of the built-in error messages. This can be very helpful when debugging code. However, at this point Squirrel will be suspended and the imp restarted with a warm boot.
Squirrel has a function along the lines of the throw
command, though their use cases are different: assert(). This triggers a runtime error if the expression or value passed as the function’s single parameter evaluates to false
. For example:
local x = 0;
try {
assert (x > 0); // x is not bigger than zero so an exception will be generated
} catch(xValueError) {
// Recover by setting a safe value of x
server.error("x not > 0 ; setting to legal value")
x = 1;
}
When the assert() condition is false, the Squirrel runtime will generate an “assertion failed” error in the log. If the assert() statement is included within a try
block, as in the example above, any exception it raises will be passed to the catch
block in the usual way. You may also want to the throw
the error on to Squirrel at this point.
Generally, assert() is used to show a programmer error: for example, it can be used to test the operation of a function by checking parameters or return values. By contrast, throw
is used to trap runtime errors that may be able to be dealt with by the program itself and so prevent Squirrel from restarting.
The most common usage of Squirrel’s error handling mechanism is to be found in agents’ HTTP request handling code. Here, a callback function of a specific type — an imp API HTTP request handler (see http.onrequest()) — will typically incorporate a try... catch
block with the intention of trapping and managing server errors which prevent the HTTP request being dealt with as anticipated.
function requestHandler(request, response) {
try {
// Check for a reset message
if ("reset" in request.query) {
resetToDefaults();
device.send("clock.set.prefs", clockPrefs);
if (server.save(clock_prefs) == 0) {
response.send(200, "Settings reset");
} else {
response.send(400, "Settings not reset");
}
return;
}
// If the command has not been recognized, inform the app
response.send(400, "Command not recognized");
} catch(error) {
response.send(500, ("Agent error: " + error));
}
}
It’s worth noting that exceptions thrown inside callbacks are not caught by try... catch
blocks outside the callback. The following code demonstrates a common mistake. The catch block will not be fired by the throw inside the imp.wakeup() block.
function requestHandler(request, response) {
try {
imp.wakeup(1.0, function() {
throw "Kapow!";
});
} catch(error) {
response.send(500, ("Agent error: " + error));
}
}
This is because the code embedded in the function is passed to imp.wakeup() as a ‘closure’ — a self-contained block of code that, when run, is beyond the scope of the try... catch
structure. As such, the error Kapow!
will not be caught by the catch
code.
The following snippet shows how try... catch
can be used to deal with incorrect input. The function set() expects an input string containing three comma-separated values; each value is an color intensity from 0 to 255. To check that the input string contains only numeric characters and commas, the code traps Squirrel’s ‘cannot convert the string’ generated by invalid input and sets default values instead.
class RGBInput extends InputPort {
name = "color input";
type = "addr,level";
red = 0;
green = 0;
blue = 0;
function set(value) {
try {
local command = split(value, ",");
red = command[0].tointeger();
green = command[1].tointeger();
blue = command[2].tointeger();
} catch (err) {
server.error("Invalid color input");
red = 255;
green = 255;
blue = 255;
}
setLevel(1, red);
setLevel(2, green);
setLevel(3, blue);
}
}