Chapter 5 - Error Handling

This tutorial is based on the error_handling.js example, which can be found in the TRAC GitHub Repository under examples/apps/javascript.

Error handling basics

The TRAC web API bindings support both futures and callback-style API calls. The same error handling capabilities are available in both styles.

As a basic example, we can send an invalid request to the server and it will respond with a validation error. Here is what that looks like using futures:

64    // Handle an error in the API call using futures
65    return metaApi.createObject(badRequest)
66        .then(_ => {})  // handle response
67        .catch(error => {
68            // handle error
69            console.log("There was a problem creating the object: " + error.message);
70            console.log("gRPC status code: " + error.code);
71        });

The error.message property contains a short human-readable description of the error. You can also access the gRPC status code using the error.code property. Both of these properties are available even if communication with the server fails, for example in the case of network errors.

Note

A list of gRPC status codes is available in public documentation for gRPC: https://grpc.io/docs/guides/status-codes/

Exactly the same error handling capability is available using callbacks:

80    // Handle the same error using callback-style API calls
81    metaApi.createObject(badRequest, (error, _) => {
82        if (error != null) {
83            // handle error
84            console.log("There was a problem creating the object: " + error.message);
85            console.log("gRPC status code: " + error.code);
86        } else {
87            // handle response
88        }
89    });

TRAC Error Details

Sometimes it is helpful to get more detailed information about an error. For example, in the case of validation failures, there can sometimes be multiple issues and we want to know exactly what those issues are and where in the input they occurred. Fortunately, TRAC has a means to provide this information.

 98    // Handle an error in the API call using futures
 99    return metaApi.createObject(badRequest)
100        .then(_ => {})  // handle response
101        .catch(error => {
102            // Get structured error details
103            const details = tracdap.utils.getErrorDetails(error);
104            console.log("There was a problem creating the object: " + details.message);
105            console.log("gRPC status code: " + details.code);

Use getErrorDetails() to get a TracErrorDetails object. The details.message and details.code properties are the same as for basic error handling and are always available. However, if the server provided more detailed information, this can be accessed by looking at the individual error items:

106            // Show more detailed information
107            details.items.forEach(item => {
108                console.log(item.fieldPath + ": " + item.detail);
109            });

For each item, item.detail is a human readable description of the error. For errors related to the request (mostly validation errors), item.fieldPath is the field path for the input field that caused the problem.

Errors in streaming calls

See also

See Chapter 4 - Streaming for more details on the streaming data API.

Errors in upload streams

With upload streams, the client sends a series of messages to the server and receives a single message or error in response. The error handler can be attached when you send the first message to the stream:

stream.createDataset(request0)
    .then(_ => {...})  // Handle upload success
    .catch(error => {
        // Handle upload error
        const details = tracdap.utils.getErrorDetails(error);
        ...
    });

// Now send the rest of the messages to the stream
stream.createDataset(msg1);
stream.createDataset(msg2);
...

Similarly if you are using callback style, a callback is only needed for the first message of the stream.

Errors in download streams

For download streams, the client sends a single request to the server and receives a stream of messages in response. An error can occur at any point in the stream and has to be handled using a stream event handler.

Because errors are processed using stream events, the futures / callback handlers should be explicitly set to no-op functions to avoid unhandled or duplicate events.

// Handle the download stream
stream.on("data", msg => {...});
stream.on("end", () => {...});

stream.on("error", error => {
    // Handle download error
    const details = tracdap.utils.getErrorDetails(error);
    ...
});

// Disable future / callback processing, because we are using stream events
stream.readDataset(request)
    .then(_ => {})
    .catch(_ => {});

Using promises for streaming operations

In Chapter 4 - Streaming, the streaming operations are wrapped up into promises and errors are passed directly to the promise resolve() method. Once the operation is wrapped up into a promise, errors can be processed using a regular .catch().

For example, the streaming download API readDataset() is wrapped up into a promise by loadStreamingData() and handled as shown in this example, hiding the details of the stream event processing:

146    return loadStreamingData(dataId)
147        .then(_ => {})  // Handle success
148        .catch(error => {
149            const details = tracdap.utils.getErrorDetails(error);
150            console.log("Download failed: " + details.message);
151            details.items.forEach(item => console.log(item.detail));
152        });