Things can and do go wrong, and when they do we need to be able to handle them in an orderly manner.
JavaScript uses the standard try...catch
and throw
exception error handling system.
Inside WASM there is no method of catching errors but it can throw exceptions.
In this section we will explore how errors are handled and how to create your own custom made exceptions.
It is possible to do something within WASM that creates an error. You could try to write to memory that is out of bounds, indirectly call a function that does not exist, or try to divide a number by zero. When an error does happen WASM will throw an error message, which gets processed outside WASM in JavaScript.
(func (export "divideByZero") (result i32) ;; Create a runtime exception by dividing by zero i32.const 1 i32.const 0 i32.div_s )
This is an example function that will try to divide a number by zero. When called WASM will throw an error.
try { // Call the function that will throw the exception wasm.instance.exports.divideByZero(); } catch (e) { // Log the exception console.log(e); }
We can put the WASM function within a try...catch
block.
This will catch the divide by zero error.
This approach can be used for any standard errors that WASM throws.
You do not need to do this with every function you call, you can just assume that everything will work correctly and just allow any exception to stop the application.
This is only useful if you know if an exception could be thrown, if it is a possibility, because you are using untrusted data or
you have designed it to fail for some reason.
You can create your own type of exceptions and pass extra information about the error when it is thrown.
This does require a number of extra parts and some setting up.
We need to create and import a WebAssembly.Tag
object, which gives all the details relating to the information that will be passed when the exception is thrown.
// Create tag const tagIntegerException = new WebAssembly.Tag({ parameters: ['i32']}); // Create options object const options = { import: { integerException: tagIntegerException } }; // Create instance of WASM with options const wasm = await WebAssembly.instantiateStreaming( fetch('app.wasm'), options);
Here we are creating a Tag
object and importing it when the WASM instance is being created.
You can look at a tag as the prototype of the exception, listing what parameters it must contain before it is thrown.
In this example we require an i32
value pushed onto the stack before calling the throw instruction.
It uses the “import” and “integerException” property names, but you can use whatever you want.
;; Import exception (used with a single i32 value) (import "import" "integerException" (tag $integerException (param i32)))
Within WASM you need to declare the tag with the same list of parameters.
In this example we expect the exception to contain an i32
value on the stack.
This list of parameters must match those used when creating the WebAssembly.Tag
object in JavaScript.
(func (export "throwIntegerException") ;; Push the exception parameters on to the stack i32.const 101 ;; Then throw the tag/exception throw $integerException )
Now that we have imported the tag, we are able to use it to throw an exception with the extra information we want to pass along with it.
In the above example we have a function that throws the exception.
We need to make sure we push the exception parameters onto the stack first.
We then finally use the throw instruction, with the label of the exception tag to be thrown.
All execution ends at this point and control is returned back to JavaScript.
Any code below the throw
instruction is not processed.
try { // Call the function that will throw the exception wasm.instance.exports.throwIntegerException(); } catch (e) { // Check it is a Web Assembly exception if (e instanceof WebAssembly.Exception) { // Check it is the right tag if (e.is(tagIntegerException) === true) { // Get the arguments const value = e.getArg(tagIntegerException, 0); // Log result console.log('IntegerException thrown value = ' + value); } else { // Log error console.log('Unknown WebAssembly.Exception'); } } else { // Log error console.log('Unknown exception'); } }
There are a number of things going on here that we need to look at step by step. The first thing we are doing is calling the WASM function that will throw the exception. After this we need to catch and handle the error, but this is more involved than before.
The error catched could be one of many different standard exceptions that WASM can throw.
Therefore we need to check if the error is one derived from WebAssembly.Exception
, and only then look at it in more detail.
It is possible there are more than just the one custom exception being used and therefore we need to workout which exception was thrown.
This can be done using the is
function, which checks if the error is from the given exception tag object.
Once we know that the error is related to the exception tag we created before, we can then get the extra information that was passed along with it.
To do this we use the error getArg
function, passing the exception tag object and the index of the parameter we want to look at.
Because our exception only has 1 parameter, we are using index 0 to get its data.
At this point we know which exception was thrown and what extra information came with it. We can then perform whatever task we need with the information supplied to handle the error.