[Fullstack Note] Javascript request in Browser and NodeJS environment

Compare with fetch and axios in source code level.

Posted by Jamie on Sunday, June 9, 2024

Preface

When it come to align fetch in both frontend and backend environment, axios is the most frequent libray which be mentioned. However, what is the diffenence between Client/Server side fetch?

Axios

We could use axios to make a request in both Browser side or server side directly. This is because axios itself handles and wraps the diffetent envireonments of the browser and Node, and then exposes them through a single interface. In the browser, axios uses XMLHttpRequest(XHR) to handle requests, while in the Node environment, it uses the library http/https. Below is a simplified demo based on the axios source code.

  • Handling in the browser — funcion xhrAdapter would return a Promise based on XMLHttpRequest.

    export async function xhrAdapter(config) {
    return new Promise((resolve, reject) => {
        const request = new XMLHttpRequest();
        request.open(config.method.toUpperCase(), config.url, true);
    
        Object.keys(config.headers).forEach((key) => {
            request.setRequestHeader(key, config.headers[key]);
        });
    
        request.onload = function () {
            const response = {
                data: JSON.parse(request.responseText),
                status: request.status,
                statusText: request.getAllResponseHeaders(),
                config: config,
                request: request,
            };
            resolve(response);
        };
        request.onerror = function () {
            reject(new Error("Network Error"));
        };
    
        request.ontimeout = function () {
            reject(new Error(`Timeout of ${config.timeout} ms exceeded`));
        };
    
        request.send(config.data);
    });
    }
    
  • In the Node environment, is httpAdpater function, which return a Promise based http or https.

    import http from "http";
    import https from "https";
    
    export async function httpAdpater(config) {
    return new Promise((resolve, reject) => {
        const url = new URL(config.url);
        const isHttps = url.protocol === "https";
        const options = {
            method: config.method,
            headers: config.headers,
        };
    
        const request = (isHttps ? https : http).request(
            url,
            options,
            (response) => {
                let data = "";
    
                response.on("data", (chunk) => {
                    data += chunk;
                });
                response.on("end", () => {
                    resolve({
                        data: JSON.parse(data),
                        status: response.statusCode,
                        statusText: response.statusMessage,
                        headers: response.headers,
                        config: config,
                        request: response,
                    });
                });
            },
        );
    
        request.on("error", (error) => {
            reject(error);
        });
        if (config.data) {
            request.write(config.data);
        }
    
        request.end();
    });
    }
    
    
  • Finally, will check the environment is in the browser or in the Node , in order to return the right instance—if browser, return xhrAdapter; if Node environment, return httpAdpater

    async function getDefaultAdapter() {
    if (typeof XMLHttpRequest !== "undefined") {
        // for browser, using XHR adapter
        const { xhrAdapter } = await import("./xhrAdapter.js");
    
        return xhrAdapter;
    } else if (typeof process !== "undefined") {
        // for Node env, using HTTP Adpater
        const { httpAdpater } = await import("./httpAdpater.js");
    
        return httpAdpater;
    }
    
    throw new Error("No suitable adapter found");
    }
    
  • The completed mocking axios source code could refer throught the link

fetch

Since 2015, there has been a new Fetch API in Browser environment, and it has become the standard in web async request. However, there are still no comparable Fetch API in Node environment, so many SDKs choose node-fetch library to make up for this.

Bascially, it is also related with checking global environment—if in Browser environment, could use native fetch directly; if in Node environment, could import node-fetch dyamicly. (no dyamicly would cause compile error)

export async function initFetch() {
    let fetch;

    if (typeof window !== "undefined" && typeof window.fetch === "function") {
        fetch = window.fetch;
    } else {
        const nodeFetch = await import("node-fetch");
        fetch = nodeFetch.default;
    }

    if (typeof global !== "undefined") {
        global.fetch = fetch;
    } else if (typeof window !== "undefined") {
        window.fetch = fetch;
    }
}

However, the latest version of node-fetch only support ESM, so it would cause compiling error in the commonjs project. In this sicuation, could replace node-fetch with http/https

import http from "http";
import https from "https";
import { URL } from "url";

function fetch(url, options = {}) {
    return new Promise((resolve, reject) => {
        const urlObj = new URL(url);
        const isHttps = urlObj.protocol === "https:";
        const { method = "GET", headers = {}, body } = options;

        const requestOptions = {
            method,
            headers,
        };

        const request = (isHttps ? https : http).request(
            urlObj,
            requestOptions,
            (response) => {
                let data = "";

                response.on("data", (chunk) => {
                    data += chunk;
                });

                response.on("end", () => {
                    const responseData = {
                        ok:
                            response.statusCode >= 200 &&
                            response.statusCode < 300,
                        status: response.statusCode,
                        statusText: response.statusMessage,
                        headers: response.headers,
                        url: response.url || url,
                        json: () => Promise.resolve(JSON.parse(data)),
                        text: () => Promise.resolve(data),
                        blob: () => Promise.resolve(Buffer.from(data)),
                    };
                    resolve(responseData);
                });
            },
        );

        request.on("error", (error) => {
            reject(error);
        });

        if (body) {
            request.write(body);
        }

        request.end();
    });
}

export default fetch;

Finally, make the global fetch pointo to the fetch function.

  • could checkt the compeleted mock fetch source code through link

Conclusion

Thurdermore, many third pary libaraies and SDKs avoid using Axios for network requests, primarily to reduce additional dependenies and minimize the bondle size. Additionally, since the native Fetch API in Node.js is only available starting from version 21, manual wrapping of the http/https modules or directly using node-fetch is still necessary for backward compatibility.

The completed source code is on github js-fetch_vs_axios

Ref

ChangeLog

  • 20240609-init