JavaScript’te Abort Controller: İstekleri Yönetme ve İptal Etme Gücü

Bu yazımda sizlere AbortController’ın ne olduğunu, neden ihtiyacınız olabileceğini ve nasıl uygulamanızın performansını artırabileceğini anlatacağım.

AbortController, JavaScript’in web API tarafından sağlanan bir özelliğidir. Bir veya daha fazla web API isteğini iptal etmenizi sağlar.

Örneğin, sunucudan veri almak için bir API oluşturduğunuzu düşünelim. Kullanıcı farklı bir işlem yaptığında bu isteği iptal etmek isteyebilirsiniz. Bu durumda AbortController’ı fetch isteğine bağlayarak bir iptal sinyali oluşturabilirsiniz. Kullanıcı, isteği iptal etmeyi gerektiren bir işlem gerçekleştirirse, iptal sinyalini tetikleyebilir ve bu isteği  durdurabilirsiniz, böylece istenmeyen verilerin alınmasını önleyebilirsiniz.

const controller = new AbortController();
const signal = controller.signal;  // İsteği iptal etmek için fetch isteğine eklenen sinyaldir.
const abort = controller.abort; // Çağırıldığı zaman isteği iptal eden metoddur.

Daha iyi anlamak için bir örnek üzerinden devam edelim; Bu örnekte; verileri dummyjson’dan fetch edeceğim.

Uygulama, üç farklı kategoriye ait verileri çağırmak için kullanıcı arayüzü sağlar: products, recipes ve users. Seçilen kategoriye göre, uygun veriler çağrılır ve görüntülenir.

import { useEffect, useState } from "react";
import { Link, Route, Routes } from "react-router-dom";
import { BrowserRouter as Router } from "react-router-dom";

interface Response {
  id: string;
  name?: string;
  title?: string;
  email?: string;
}

interface Data {
  [key: string]: Response[];
}

function App() {
  const [url, setUrl] = useState("products");
  const [data, setData] = useState<Data>({});
  const [isLoading, setIsLoading] = useState(false);
  const currentPageData = data[url];

  useEffect(() => {
    setIsLoading(true);
    fetch(`https://dummyjson.com/${url}`)
      .then((res) => res.json())
      .then((res) => {
        setData(res);
        setIsLoading(false);
      });
  }, [url]);

  return (
    <Router>
      <div className="App" style={{ padding: "20px" }}>
        <div className="nav" style={{ display: "flex", gap: "10px" }}>
          <Link
            to="/products"
            onClick={() => setUrl("products")}
            style={{
              color: "white",
              backgroundColor: "black",
              padding: "5px",
              textDecoration: "none",
            }}
          >
            Products
          </Link>
          <Link
            to="/recipes"
            onClick={() => setUrl("recipes")}
            style={{
              color: "white",
              backgroundColor: "red",
              padding: "5px",
              textDecoration: "none",
            }}
          >
            Recipes
          </Link>
          <Link
            to="/users"
            onClick={() => setUrl("users")}
            style={{
              color: "white",
              backgroundColor: "blue",
              padding: "5px",
              textDecoration: "none",
            }}
          >
            Users
          </Link>
        </div>
        <div className="page" style={{ marginTop: "20px" }}>
          <Routes>
            <Route path="/products" element={<div>Products Page</div>} />
            <Route path="/recipes" element={<div>Recipes Page</div>} />
            <Route path="/users" element={<div>Users Page</div>} />
          </Routes>
          {isLoading ? (
            "Loading Data..."
          ) : (
            <ul>
              {Array.isArray(currentPageData) &&
                currentPageData.map((item) => (
                  <li key={item.id}>{item.name || item.title || item.email}</li>
                ))}
            </ul>
          )}
        </div>
      </div>
    </Router>
  );
}

export default App;

Görüldüğü üzere tüm istekler başarılı olarak dönüp ekrana yazdırıldı. Daha hızlı şekilde menüde gezinip, gereksiz istekler yaptığımız bir durumu hayal edebiliriz burada.

Örneğin: kullanıcı “/users” sayfasına gidiyor ve bir istek başlıyor ama daha sonra isteğe cevap gelmeden kullanıcı “/products” sayfasına gidiyor, şu anda onun artık “/users” sayfasındaki verilere ihtiyacı olmadığını varsayıyoruz. Bu durumda, kullanıcının “/users” sayfasına yaptığı isteği iptal etmek için Abort Controller’ı kullanabiliriz.

const controller = new AbortController();
const signal = controller.signal;

useEffect(() => {
  const fetchData = async () => {
    try {
      setIsLoading(true);
      const response = await fetch(`https://dummyjson.com/${url}`, {
        signal,
      });
      const data = await response.json();
      setData(data);
      setIsLoading(false);
    } catch (error) {
      console.error("Error fetching data: ", error);
      setIsLoading(false);
    }
  };

  fetchData();

  return () => {
    controller.abort();
  };
}, [url]);

Görselde görüldüğü üzere gereksiz istekler iptal edildi!

Axios

Fetch ile aynı şekilde kullanılmaktadır.

axios.get("someurl", { signal: abortController.signal })

GraphQL

GraphQL’de de hemen hemen aynı şekilde kullanılmaktadır, tek fark useRef ile birlikte kullanılmalıdır.

const controllerRef = useRef(new AbortController());

const [getQuery, { data }] = useLazyQuery(SOME_QUERY_MODEL, {
  context: {
    fetchOptions: {
      signal: controllerRef.current.signal,
    },
  },
});

useEffect(() => {
  return () => controllerRef.current.abort();
}, []);