Search code examples
javascriptfetch

Multiple fetches with different timeouts - Vanilla JS (no nesting)


I have data coming from two APIs and each has a different request limit (and also what makes sense for a fetch frequency). I managed to make one work, but having two overwrites the first one and only fetches the second request. How do I combine two fetch requests with different timeouts to make both work?

this is the fetch structure I'm working with currently:

function fecske() {      
       fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=10&page=1&sparkline=true')
            .then(function (response) { return response.json(); })
            .then(function (data) { appendData(data); });    setTimeout(fecske, 2000); }; fecske()
    
   function appendData(data) { 
//applying stuff here to different divs
 }

nesting one into the other wont work as one has to request every 2-5 seconds the other one should request once an hour - so nesting the once an hour into the few seconds would call that as well too often (if I'm right?)


Solution

  • You've said that the two API requests have to happen at different rates, and that they aren't related to each other — you don't need information from one of them to do the other or anything like that.

    First, a caveat: Repeatedly polling like this is almost always an anti-pattern, and it's something that browsers are actively moving to throttle. Here's an article by Jake Archibald about throttling in Chrome and, crucially, alternatives to polling available in modern browsers.

    But back to the polling. If they're really independent, you literally just keep them complete separate. You could do that by writing the same thing twice with different URLs and options and such...

    function doRequestA() {
        fetch("https://example.com/request/A")
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            // ...do something with `data`...
        })
        .catch(error => {
            // ...handle/report error...
        })
        .finally(() => {
            if (shouldKeepDoingRequestA) {
                setTimeout(requestA, interval);
            }
        });
    }
    
    function doRequestB() {
        fetch("https://example.com/request/B")
        .then(response => {
            if (!response.ok) {
                throw new Error(`HTTP error ${response.status}`);
            }
            return response.json();
        })
        .then(data => {
            // ...do something with `data`...
        })
        .catch(error => {
            // ...handle/report error...
        })
        .finally(() => {
            if (shouldKeepDoingRequestB) {
                setTimeout(requestB, interval);
            }
        });
    }
    

    ...or (and this is what I would do) you could give yourself a utility function to do it; here's the latter:

    // Utility function that gets JSON with appropriate HTTP check
    function fetchJSON(...args) {
        return fetch(...args)
            .then(response => {
                if (!response.ok) {
                    throw new Error(`HTTP error ${response.status}`);
                }
                return response.json();
            });
    }
    
    // Utility functon for repeated JSON requests that you can cancel when needed
    function startRepeatJSONRequest({url, options = undefined, callback, signal, interval = null}) {
        function doRequest() {
            fetchJSON(url, {...options, signal})
            .then(data => {
                if (!signal.aborted) {
                    callback(data);
                }
            })
            .catch(error => {
                // ...handle/report error...
            })
            .finally(() => {
                if (!signal.aborted && interval !== null) {
                    setTimeout(doRequest, interval);
                }
            });
        }
        doRequest();
    }
    
    // Repeated requests for API A
    const controllerA = new AbortController();
    startRepeatJSONRequest({
        url: "https://example.com/request/A",
        callback(data) {
            // ...use `data` here...
        },
        signal: controllerA.signal,
        interval: 3000, // or whatever
    });
    
    // Repeated requests for API B
    const controllerB = new AbortController();
    startRepeatJSONRequest({
        url: "https://example.com/request/A",
        callback(data) {
            // ...use `data` here...
        },
        signal: controllerB.signal,
        interval: 5000, // or whatever
    });
    

    Note the use of AbortController/AbortSignal so you can cancel the process when you don't need it anymore.


    In both of the above, note the check on response.ok in the first fulfillment handler on the fetch calls. This is to deal with what is (IMHO) a footgun in the fetch API: It only rejects its promise on network errors, not HTTP errors (like 404s or 500s). For those it fulfills its promise, but the Response object's ok property is false and the status has the HTTP status. I wrote this up here on my anemic old blog.