Obecnie jest moda na aplikację reaktywne, responsywne etc. Tego typu aplikacje bardzo często odświeżają dane przy pomocy zapytań AJAX-owych. Weźmy na warsztat przypadek, w którym podczas request-u stan aplikacji frontend-owej ulegnie zmianie i request przestanie być aktualny. W takiej sytuacji programistom na pomoc przychodzi funkcją "abort" z prototypu obiektu XMLHttpRequest i to właśnie chciałbym omówić.
Opis działania funkcji abort
Zgodnie z dokumentacją zamieszczoną na Mozilla Developers Network (MDN) wywołanie funkcji abort na instancji XMLHttpRequest anuluje zapytania, które już zostały wysłane do serwera.
Skąd to się bierze? Jak przebiega cały proces? Magia jakaś czy co?
Wywołanie funkcji "abort" ustawia dwa bardzo ważne klucze w instancji XMLHttpRequest:
- ReadyState na wartość 4 (XMLHttpRequest.DONE) co w skrócie oznacza zakończenie żądania
- Status na wartość 0, co oznacza że request został ubity przez przeglądarkę (bardzo często przy błędach związanych z CORS-em dostajemy status 0)
Teoria jest piękna... w teorii. A jak wszyscy wiemy najlepiej uczyć się poprzez praktykę, więc...
...Piszemy aplikację testową!
Zaczynając od serwera
No dobrze, ale skąd ma serwer wiedzieć że nie musi obsługiwać tego zapytania?
To jest bardzo dobre pytanie! Jego autor dostał ode mnie pączka za wnikliwość. No dobra, sam je sobie zadałem... Pomijając jakość wyrobu cukierniczego (który swoją drogą ładniej wyglądał niż smakował) poczułem ogromną potrzebę zobrazowania działania kryjącego się za działaniem funkcji abort. Aby lepiej wszystko zilustrować napisałem prosty serwer w node.js, którego jedynym zadaniem jest zliczanie ile request-ów zostało wysłanych na podany endpoint. Do stworzenia tego posłużyłem się frameworkiem express.js, dzięki czemu całą implementację zmieściłem w 18 liniach kodu:
const express = require('express')
const app = express()
let api = 0;
app.use(express.static('static'))
app.get('/api', (req, res) => {
let value = api++;
setTimeout(() => {
res.send(value + '')
}, 10)
})
app.listen(3000, () => {
console.log('Example app listening on port 3000!')
})
W obsłudze odpowiedzi pod adresem "/api" wykorzystałem funkcję setTimeout, aby chociaż minimalnie zasymulować opóźnienie związane z przesyłaniem pakietów po sieci (zapytania AJAX-owe na localhoście są bardzo szybkie :))
Teraz czas na szybki opis stworzonego przeze mnie frontendu aplikacji
Tworzymy frontend
W katalogu static swojego projektu stworzyłem plik "index.html", który posłuży mi do wysyłania i abortowania requestów. Cała implementacja tego zjawiska zajęła mi 21 lini kodu
<script>
{
function sendRequest(doAbort = true) {
var req = new XMLHttpRequest();
req.open('GET', '/api', true);
req.send(null);
if (doAbort) {
req.abort();
}
}
sendRequest(false);
sendRequest();
sendRequest();
sendRequest();
sendRequest();
sendRequest();
sendRequest(false);
}
</script>
Mam świadomość powtarzalności kodu przy wysyłaniu zapytań, które potem abortuje, jednak to jest tylko aplikacja typu "proof of concept".
Więc jak wyglądają wyniki naszego małego eksperymentu?
Podsumowanie
Po wywołaniu aplikacji pod adresem http://localhost:3000/index.html w konsoli developerskiej Chrome-a ujrzałem wysłane requesty
Jak widać na załączonym obrazku poprawnie wysłano dwa requesty: pierwszy i ostatni. request zostały wysłane poprawne, natomiast wszystko pomiędzy zostało anulowane.
Pierwszy request oczywiście otrzymał zwrotkę z cyfrą zero (0)
Natomiast najciekawszy jest ostatni request, który pomoże mi zobrazować co się dzieje na serwerze. To co zostało zwrócone przez serwer możecie zobaczyć na poniższym obrazku
Serwer zwrócił cyfrę 6, co ujawnia smutną prawdę - poprzednie żądania zostały całkowicie przetworzone przez serwer :(
Słowo końcowe
Jak widać funkcja abort wcale nie ubija całego request-u, tylko wyłącza jego obsługę po stronie frontendu. Z tego powodu polecam wam rozejrzenie się za jakimś innym rozwiązaniem np. debounc-em.
Całą aplikację możecie znaleźć na moim Github-ie Tutaj
Mam nadzieje, że post się Wam przyda i do następnego!
Cześć!