How to combine debounce and Abort Controller for axios

325 views Asked by At

I have the following search bar with

        <div class="d-flex">
            <input class="form-control me-2" type="search" placeholder="Search Products" aria-label="Search" id="searchProduct">
        </div>

I would like to fire an onInput event whenever user types it will call an API.

const debounce = (fn, delay = 1000) => {
  let timerId = null;
  return (...args) => {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn(...args), delay);
  };
};
let controller = new AbortController();
let signal = controller.signal;

const fetchProducts = (value, signal) => {
  axios
    .get(`/FYP-ECOMMERCE/api/products?search=${value}`, {
      signal, // Pass the AbortSignal to the request
    })
    .then((response) => {
      return response.data;
    })
    .then((data) => {
      console.log(data);
    })
    .catch((error) => {
      if (error.name === "AbortError") {
        // Handle the request abortion (e.g., ignore or log it)
        console.log("Request was aborted");
      } else {
        // Handle other errors
        console.error("An error occurred:", error);
      }
    });
};

const onInput = debounce(fetchProducts, 500);

const searchBar = document.getElementById("searchProduct");

searchBar.addEventListener("input", (e) => {
  const term = e.target.value;
  if (term !== "") {
    controller.abort();
    controller = new AbortController();
    signal = controller.signal;
    onInput(e.target.value, signal);
  }
});

So my question how to combine debounce and abortcontroller , so that the previous request gets aborted.

Thank you

2

There are 2 answers

1
brandonwie On
  1. return controller in fethcProducts fn === create a new controller for each request
const fetchProducts = (value, signal) => {
  const controller = new AbortController();

  axios
    .get(`/FYP-ECOMMERCE/api/products?search=${value}`, {
      signal: controller.signal,
    })
    .then((response) => {
      return response.data;
    })
    .then((data) => {
      console.log(data);
    })
    .catch((error) => {
      if (error.name === "AbortError") {
        console.log("Request was aborted");
      } else {
        console.error("An error occurred:", error);
      }
    });
  return controller
};

this will give you control over the controller

  1. Make the onInput a general function, add controller as param so that if controller exists, the current request will be aborted and fetch again with a new controller
// ASIS
const onInput = debounce(fetchProducts, 500);

// TOBE
const onInput = debounce((fn, value, controller) => {
  if (controller) controller.abort();
  return fn(value); // if you make a fetch fn that returns a controller, you can reuse it.
}, 500);
  1. Other codes
let currController;

const searchBar = document.getElementById("searchProduct");

// Separate handler so that you can remove the event
function searchBarHandler(e) {
  const term = e.target.value;
  // `if` transforms the `term` as boolean to evaluate, "" will return false
  if (term) {
    currController = onInput(fetchProducts, term, currController)
  }
} 

searchBar.addEventListner("input", searchBarHandler);

the onInput clearly can be written in a better way, but for now, I think this should do

0
Khoa On

Within your approach, the onInput will be called every time user click button. However, thanks to debounce, the truth fetchProducts will be called only once every 500ms and no more.

Therefore if you just simply need to call "abort" at the beginning of fetchProducts

// 1. make your controller global and `null`
let controller = null

// 2. you don't need `signal` here
const fetchProducts = (value) => {
    if (controller) {
        controller.abort()
    }

    controller = new AbortController()

    axios
        .get(`/FYP-ECOMMERCE/api/products?search=${value}`, {
             controller.signal, // from signal to controller.signal
        })
        // put the rest of your code here...
}

You can learn more about it with the example from MDN https://developer.mozilla.org/en-US/docs/Web/API/AbortController

Bonus:

Since the controller is outside the fetch function, if you have a Cancel/Abort button, just simply do the same

onCancelClick() {
    if (controller) {
        controller.abort()
    }
}