Almost every real-world application relies on data fetching and some sort of asynchronous interaction with an external API. Just like any programming problem, there are quite a number of ways to solve this; some are simple, others are complex. One common theme, however, is that the solutions usually have one or more features missing, like interpolation, re-fetch, etc.
In the browser environment, we are provided with the fetch
API, which is simple and relatively flexible. However, it comes with a problem: it assumes all non-success (≥4xx) exceptions and throws them. Hence we are limited to catching these exceptions with a Promise.catch
or the try/catch
in an async/await
pattern.
The issue with this is that those thrown exceptions are notoriously difficult to type since they could be of any shape. In retrospect, we know that we usually have an idea of the shape of our errors if we are allowed to deal with them. Here comes gretchen
.
gretchen
offers a sound parity with fetch
out of the box, which makes it easily adaptable in legacy codebases. It offers resilience to common request/response issues like errors and inconsistent responses returned from APIs. This is handled via built-in timeout handling, configurable retry logic, and error normalization. It also offers good browser support as far back as Internet Explorer 11.
You can install via either yarn
or npm
npm install gretchen OR yarn add gretchen
With fetch
, you would have something like:
const request = await fetch("/api/dogs/1"); const dog = await request.json();
Similarly, with gretchen
, you’d have:
import { gretch } from "gretchen"; const { data: dog1} = await gretch("/api/dog/1").json();
You will notice this provides just enough abstraction, baking in the ease of use of fetch
without sacrificing flexibility. One other important difference to note is how you can resolve to JSON
within one statement.
gretchen
gretchen
really begins to shine and differentiate itself from native fetch
in error handling. The most common option is to return a typed entity upon which you can then make assertions. This will give TypeScript an opportunity to understand what we are dealing with.
try { const cats = await getAllCats(); return cats } catch (e) { if (error instanceof ErrorType) { // handle Error } }
The problem with this is that you have to manually check whether it satisfies the condition. Admittedly, you could write a middleware to perform this check, but that is an unnecessary additional step and adds boilerplate. The ideal solution would be to not have to perform this check at all, and to do that elegantly, with less code.
Another option would be to not throw any exceptions. fetch
works this way by default. So we are able to configure our response based on whether we get the required data — that is, for success and error responses, we can abstract away the assertion and handle the response elegantly. This is similar to the RESULT
type in Rust.
enum Result<T, E> { Ok(T), // success data Err(E), // error }
We could then have something like this for handling responses:
if (response.status < 300) {return Result.Ok(response); } else { return Result.Err(response); }
While this pattern might be appealing, it introduces a layer of complexity that might not be intuitive for newcomers. gretchen
tries to simplify this by providing a much clearer abstraction in the absence of exceptions.
Instead of returning an object with methods for checking the type of the discriminated union, gretchen
has employed a pattern similar to the error handling found in Go. Both halves of the union are returned as separate entities:
const { error, data } = await getEndpoint(); if (error) { // handle error } else { // handle data }
const response = await fetch("/user/12345"); const { ok, status } = response;
You can then handle the response if error is not ok
or handle a success response.
Non-GET requests are quite similar to GET
requests, but just like fetch
, you will need to pass in the method option to fetch, like so:
const { status, error, data: user } = await gretch("/note/create", { method: "POST", json: { title: "Take over", text: "We are the world" } }).json(); if (error) { // handle error } else { // handle user }
Internally, gretchen
already resolved most of the boilerplate you’d encounter with fetch
, like awaiting response and then converting to JSON like arrayBuffer
, blob
, formData
, json
, and text
.
By default, gretchen
will retry GET requests that return 408, 413, or 429 two times. Failure can occur due to network issues or server load. This gives you the flexibility to retry these requests, and you can, of course, override the default number of retries like so:
await gretch("/notes", { method: "POST", retry: { attempts: 9, // number of retry methods: ["POST"] }, json: { title: "Take over", text: "We are the world" } }).json();
This is similar to retrying a request. A request is timed out by default after 10 seconds. You can optionally configure the timeout time.
gretchen
with TypeScriptYou can specify the type of both your error and response. This is straightforward with the generic interface that gretchen
allows.
The typing in the gretchen
source code looks something like this:
gretch<T = DefaultGretchResponse, A = DefaultGretchError>
You will notice that a default response and error type are included, hence, you can pass your error and data response type in the same manner. For instance:
interface Note { id: number author: string title: string text: string } interface CustomError { message: string code: number }; const { error, data } = await gretch<Note, CustomeError>( "/note/1" ).json();
Tying this together, you should have a robust response-error handling that looks like this:
interface Note { id: number author: string title: string text: string } interface CustomError { message: string code: number }; const { error, data } = await gretch<Note, CustomeError>( "/note/1" ).json(); error ? handleError() : handleSuccess()
But why gretchen
, I hear you ask? We already have similar abstractions that do similar jobs. That is not entirely true. gretchen
goes a few steps further by allowing type-safe fetching by providing a very subtle abstraction over the already popular fetch
syntax that you already know and love.
Additionally, gretchen
makes it easy to elegantly type both the expected data and any errors that might occur. This might not seem important at first, but as your application grows in complexity, the ability to safely type your API response goes a long way in providing clarity to any client consuming them.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
There’s no doubt that frontends are getting more complex. As you add new JavaScript libraries and other dependencies to your app, you’ll need more visibility to ensure your users don’t run into unknown issues.
LogRocket is a frontend application monitoring solution that lets you replay JavaScript errors as if they happened in your own browser so you can react to bugs more effectively.
LogRocket works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Build confidently — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.