Error Handling
Introduction
Section titled “Introduction”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.
The Path of a Pipeline Error
Section titled “The Path of a Pipeline Error”- The request fails.
PipelineManagerbuilds an error object with thecodeand the message from the response. - 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. - The dispatched Redux action reaches the
pipelineError$stream. - 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.
What the User Sees
Section titled “What the User Sees”The subscription never displays the raw message of the backend. It resolves the text in the following order:
- The message of the extension, if the error is flagged as already translated.
- The message, if it is a translation key that resolves in the loaded locales.
- The message that
errorManagerproduced for the error code, if it differs from the raw message of the backend. - 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.
Showing Your Own Message
Section titled “Showing Your Own Message”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.
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.
Handling an Error Yourself
Section titled “Handling an Error Yourself”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']);Errors That Are Only for Logging
Section titled “Errors That Are Only for Logging”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.
Errors While Rendering
Section titled “Errors While Rendering”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.
Errors in the Backend
Section titled “Errors in the Backend”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.
Error Codes
Section titled “Error Codes”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 |

