DEV Community

Cover image for Decorating The Fetch API
Raymond Ottun
Raymond Ottun

Posted on

Decorating The Fetch API

Like most full stack javascript developers, I have come to love the web Fetch API. It is a simple and elegant way to make http requests but the Fetch API lacks some features (and rightly so) that are essential for making http requests in a production environment. Here are some of those features:

In this post, I will show how to use the decorator pattern to enhance the Fetch API and add some of these features without modifying the Fetch API itself.

What is a decorator?

A decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. They key point is that a decorator function returns a modified version of the original function.

Logging decorator

The simplest decorator to implement is the logging decorator. It simply logs the url and the response of the http request. Here is the code:

type Fetch = (...args: any) => Promise<Response>;

interface Logger {
    log: (message: string) => void;
    info: (message: string) => void;
    error: (message: string) => void;
    warn: (message: string) => void;
}

export const withLogger = (fetch: (...args: any) => Promise<Response>, logger: Logger = console): Fetch => {
    return async (...args) => {
        const [input] = args;
        const response = await fetch(...args);
        if (typeof input === "string") {
            logger.info(`fetching ${input} and got ${response.status}`);
        }
        else if (input instanceof URL) {
            logger.info(`fetching ${input} and got ${response.status}`);
        }
        else {
            logger.info(`fetching ${input.url} and got ${response.status}`);
        }
        return response;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can use the withLogger decorator to log the url and the response of the http request.

// This behaves exactly like the fetch api
const fetchWithLogger = withLogger(fetch);
// This will log the url and the response of the http request
fetchWithLogger("https://jsonplaceholder.typicode.com/todos/1");
Enter fullscreen mode Exit fullscreen mode

Retry decorator

A more useful decorator is the retries decorator. It retries the http request if it fails. Here is the code:

interface RetryOptions {
    retryCount: number
    retryDelay?: number
    retryOn?: number[]
}

export const withRetry = (fetch: Fetch, options: RetryOptions) => {
    const { retryCount = 1, retryDelay = 1000, retryOn = [500] } = options;
    let response: Response;
    return async (...args: any): Promise<Response> => {
        let retry = 0;
        while (retry < retryCount) {
            response = await fetch(...args);
            if (retryOn.includes(response.status)) {
                retry++;
                await new Promise((resolve) => setTimeout(resolve, retryDelay));
            }
            else {
                return response;
            }
        }
        return response;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can use the withRetry decorator to retry the http request if it fails.

// This behaves exactly like the fetch api
const fetchWithRetry = withRetry(fetch, { retryCount: 3 });
// This will retry the http request if it fails
fetchWithRetry("https://jsonplaceholder.typicode.com/todos/1");
Enter fullscreen mode Exit fullscreen mode

Chaining decorators

One of the advantages of using a decorator is that they can be chained together to even further extend the behavior of a function. This is called composition. Here is an example of how to use the withLogger and withRetry decorators together.

const fetchWithRetryAndLogger = withLogger(withRetry(fetch, { retryCount: 3 }));
// This will retry the http request if it fails and log the url and the response of the http request
fetchWithRetryAndLogger("https://jsonplaceholder.typicode.com/todos/1");
Enter fullscreen mode Exit fullscreen mode

This github repository contains the full code for the decorators discussed in this post.

Conclusion

The decorator pattern is a simple and elegant way to extend the behavior of a function without explicitly modifying it. It is a powerful tool in the hands of a developer and can be used to solve a lot of problems. In this post, I have shown how to use the decorator pattern to enhance the Fetch API and add some features that are essential for making http requests in a production environment.

References

Top comments (0)