Skip to content

Error Handling

Errors reach the user through one place in the app. A failing pipeline request rejects the promise of the PipelineRequest, and in parallel the error is queued and dispatched into Redux, where a single subscription decides whether the user sees a toast, a modal, or nothing at all.

This guide describes that path and how an extension influences it. It covers the four cases an extension has to distinguish:

  • Errors with a message for the user.
  • Errors that result in a generic message.
  • Connection errors.
  • Errors that are only relevant for logging.
  1. The request fails. PipelineManager builds an error object with the code and the message from the response.
  2. Unless the error is suppressed, it is passed to errorManager.queue(). The queue removes duplicates by source, pipeline, and code, and dispatches every 500 milliseconds. A product page that fires three requests which all fail with the same code therefore produces one message, not three.
  3. The dispatched Redux action reaches the pipelineError$ stream.
  4. The subscription on that stream determines the message and its presentation.

The promise of the request rejects in every case, including when the error is suppressed. The rejected error carries a handled property that states whether the app displays the error.

The subscription never displays the raw message of the backend. It resolves the text in the following order:

  1. The message of the extension, if the error is flagged as already translated.
  2. The message, if it is a translation key that resolves in the loaded locales.
  3. The message that errorManager produced for the error code, if it differs from the raw message of the backend.
  4. A generic, translated message otherwise.

The result decides the presentation:

Error Presentation
ETIMEOUT, ENETUNREACH, or a message that resolves to the general error text Toast with the connection error text
EUNKNOWN, or an error for which only the generic message remains Toast
Every other error with a resolved message Modal with an OK button
An error during rendering Modal with the generic title and text, followed by a navigation step back

Long-pressing an error toast opens the modal with the developer details of the error: pipeline, code, raw message, and request input.

Register a message for an error code with errorManager.setMessage(). Use a translation key rather than a text, so that the message follows the language of the app. Register the message in a subscriber on appWillStart$, before any request can fail.

frontend/subscriptions.js
import { appWillStart$, errorManager } from '@shopgate/engage/core';
export default (subscribe) => {
subscribe(appWillStart$, () => {
errorManager.setMessage({
code: 'ELOYALTY',
context: 'myAwesomeOrganization.getLoyaltyPoints.v1',
message: 'myAwesomeOrganization.loyalty.error_unavailable',
});
});
};

context is the name of the pipeline. Omit it to register the message for the code regardless of which pipeline produced it. The pipeline version is optional: a message registered without the .v1 suffix also applies to other versions of the same pipeline.

message also accepts a function. It receives the error object and returns the key or the text, which allows you to distinguish several cases within one code.

Add the key to the locale files of your extension, as described in the guide about Using Translations. A key without a translation is displayed as the key itself, which is why the resolver only accepts keys that exist in the loaded locales.

Some errors are part of the normal flow of a feature and should not produce a message of their own — a validation error next to the input field that caused it, for example. Exclude the code from the standard handling and react to the rejected promise instead.

import { PipelineRequest } from '@shopgate/engage/core';
const request = new PipelineRequest('myAwesomeOrganization.getLoyaltyPoints.v1')
.setInput({ productId })
.setErrorBlacklist(['EVALIDATION'])
.dispatch();
try {
const { points } = await request;
} catch (error) {
if (error.code === 'EVALIDATION') {
// Display the error at the form field.
}
}

The following methods control the behavior of a request:

Method Effect
setErrorBlacklist(codes) The listed codes are not displayed by the app. Every other code is.
setHandleErrors(ERROR_HANDLE_SUPPRESS) No error of this request is displayed.
setResponseBehavior({ error }) The callback is executed instead of the standard presentation.

setResponseBehavior is only used for errors that resolve to a message of their own. Unknown and connection errors are always displayed as a toast, so a callback that expects a specific error is not executed with an error it cannot handle.

To exclude a code across the whole app rather than per request, use the pipeline manager:

import pipelineManager from '@shopgate/pwa-core/classes/PipelineManager';
pipelineManager.addSuppressedErrors(['ELOYALTY']);

An error that the user does not need to see should still be recorded. Suppress its presentation and log it.

import { PipelineRequest, logger, ERROR_HANDLE_SUPPRESS } from '@shopgate/engage/core';
const request = new PipelineRequest('myAwesomeOrganization.trackEvent.v1')
.setInput({ event })
.setHandleErrors(ERROR_HANDLE_SUPPRESS)
.dispatch();
try {
await request;
} catch (error) {
logger.error('Could not track the event', error);
}

The app forwards every error that it displays, every uncaught exception, and every tracking error to the error reporting service, together with the last route changes and the recent Redux actions. Errors that you suppress are not forwarded automatically.

For logging in a backend step, see the Logger reference.

A component that throws during rendering is caught by the error boundary of the app. The boundary dispatches an app error, which produces the generic modal and navigates one step back, so that the user does not remain on a broken page.

An extension does not have to register anything for this. Keep in mind that the user only sees the generic message in this case, so a failure in your component is indistinguishable from any other app error. Catch the cases you can predict in the component itself and render a fallback.

A step that fails ends the pipeline. To react to the failure inside the pipeline instead, add an Error Catch Extension Step. It receives the error and can end the pipeline with a different result.

The message that a step returns with its error reaches the frontend as the raw message of the backend, and the frontend only displays it when it is a translation key that exists in the app or when the error is flagged as translated. Use a code and register the message in the frontend as described above. That keeps the message translatable and prevents an internal text from reaching the user.

The following codes are defined by the platform. Use them where they apply instead of introducing your own.

Code Meaning
ETIMEOUT The request timed out
ENETUNREACH The device has no connection
EACCESS The user is not allowed to perform the request
EINVALIDCREDENTIALS The credentials are not valid
EINVALIDCALL The request is malformed
EVALIDATION The input did not pass validation
ENOTFOUND The requested entity does not exist
EEXIST The entity already exists
ELIMIT A limit was reached
EUNKNOWN An internal error that the user cannot act on