Caliman
Caliman

Reputation: 232

How to set a timeout using axios without cancelling API call?

Bit of background, I have this API call that can be quite lengthy in its response (talking about over a minute in some instances, but 10-15 seconds for the most part). What I'd like to do is set a timeout on the client side while the backend continues to process the call. I'm using axios to handle http request and I know there is a timeout key that is default is 0 meaning that theres no timeout so the call will continue until either succeeds or fails. I tried to set it to 1 to see how this would handle a one millisecond timeout and the call is cancelled...which makes sense. My question now is, how can I implement a timeout on the client side without cancelling the HTTP request? Some code to get your head around what I've tried.

import React from "react";
import axios from "axios"

function App() {
  const fetchLongRequest = async () => {
    try{

// All peachy over here if no timeout is implemented...

      const myRequest = await axios({
        url: "https://jsonplaceholder.typicode.com/todos/1",
        headers: {
          accept: "application/json",
          "Content-Type": "application/json"
        },
      })
      console.log("SUCCESS!", JSON.stringify(myRequest.data, null, 2))
    }catch(error){
      console.log("FAIL!", error.message)
    }
  }

  return (
   <button onClick={() => fetchLongRequest()}>Fetch</button>
  );
}

export default App;

now this is my introduction of the timeout

import React from "react";
import axios from "axios";

function App() {
  const fetchLongRequest = async () => {

// timeout works as expected but I'd like to let the call go to the backend and do its thing. 

    try {
      const myRequest = await axios({
        url: "https://jsonplaceholder.typicode.com/todos/1",
        headers: {
          accept: "application/json",
          "Content-Type": "application/json",
        },
        timeout: 1,
      });
      console.log("SUCCESS!", JSON.stringify(myRequest.data, null, 2));
    } catch (error) {
      console.log("FAIL!", error.message);
    }
  };

  return <button onClick={() => fetchLongRequest()}>Fetch</button>;
}

export default App;

I know the request is a bit odd as it opens many questions such as error handling, how to know when this call is done, etc. I'd like to get some feedback in how I can achieve this task...please :)

Upvotes: 4

Views: 18829

Answers (2)

Jaromanda X
Jaromanda X

Reputation: 1

All you need is a timeout set BEFORE the request

import React from "react";
import axios from "axios";

function App() {
    const fetchLongRequest = async () => {
        
        const waitTime = 5000;
        setTimeout(() => console.log("Request taking a long time"), waitTime);
        try {
            const result = await axios({
                url: "https://jsonplaceholder.typicode.com/todos/1",
                headers: {
                    accept: "application/json",
                    "Content-Type": "application/json",
                }
            });
            console.log("SUCCESS!", JSON.stringify(result.data, null, 2));
        } catch(error) {
            console.log("FAIL!", error.message);
        }
    };
    return <button onClick = {() => fetchLongRequest()}>Fetch </button> ;
}
export default App;

The original solutions below are total overkill!!

I think this will do what you want, uses Promise.race

note: this is still not quite right as far as error handling goes

the handleError function is purely so if a the request fails before the timeout the failure isn't output twice

import React from "react";
import axios from "axios";

function App() {
    const fetchLongRequest = async () => {
        
        const waitTime = 5000;
        const handleError = error => {
            // this makes sure that the FAIL output isn't repeated in the case when there's a failure before the timeout
            if (!error.handled) {
                if (error.timedout) {
                    console.log("TIMEDOUT", error.timedout);
                } else {
                    console.log("FAIL!", error.message);
                    error.handled = true;
                    throw error;
                }
            }
        };
        const makeRequest = async () => {
            try {
                const result = await axios({
                    url: "https://jsonplaceholder.typicode.com/todos/1",
                    headers: {
                        accept: "application/json",
                        "Content-Type": "application/json",
                    }
                });
                console.log("SUCCESS!", JSON.stringify(result.data, null, 2));
            } catch(error) {
                return handleError(error);
            }
        };
        const timer = new Promise((_, reject) => setTimeout(reject, waitTime, {timedout: "request taking a long time"}));
        try {
            await Promise.race([makeRequest(), timer]);
        } catch(error) {
            handleError(error);
        }
    };
    return <button onClick = {() => fetchLongRequest()}>Fetch </button> ;
}
export default App;

As a side note, this code is far cleaner without async/await - though, to be fair, I'm not as fluent using async/await as I am with Promises alone - I've used Promises since before there was a .catch :p

non async/await implementation

import React from "react";
import axios from "axios";

function App() {
    const fetchLongRequest = () => {
        
        const waitTime = 5000;
        const handleError = error => {
            // this makes sure that the FAIL output isn't repeated in the case when there's a failure before the timeout
            if (!error.handled) {
                if (error.timedout) {
                    console.log("TIMEDOUT", error.timedout);
                } else {
                    console.log("FAIL!", error.message);
                    error.handled = true;
                    throw error;
                }
            }
        };
        
        const myRequest = axios({
            url: "https://jsonplaceholder.typicode.com/todos/1",
            headers: {
                accept: "application/json",
                "Content-Type": "application/json",
            }
        }).then(result => {
            console.log("SUCCESS!", JSON.stringify(result.data, null, 2));
        }).catch(handleError);
        
        const timer = new Promise((_, reject) => setTimeout(reject, waitTime, {timedout: "request taking a long time"}));
        
        return Promise.race([myRequest, timer]).catch(handleError);
    };
    return <button onClick = {() => fetchLongRequest()}>Fetch </button> ;
}
export default App;

Of course "cleaner" is just my opinion

Upvotes: 11

Piyush Rana
Piyush Rana

Reputation: 667

Axios API call is promise-based so you could simply use then and catch block to perform some tasks on completion without using await then it'll simply run in background without blocking client-side. Using timeout for such scenario is not permissible because on a slow network it may take a minute to complete and once your timeout completed it'll will block client-side. Instead of using await which will block your request remove it and it simply run asynchronously which I think you wanna achieve.

const myRequest = axios({
        url: "https://jsonplaceholder.typicode.com/todos/1",
        headers: {
          accept: "application/json",
          "Content-Type": "application/json"
        },
      })

Upvotes: 1

Related Questions