On this page

Exceptions

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.

Standard Errors

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.

Custom Errors

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.