In questo articolo vedremo come reperire i dati da un'API REST con le Fetch API gestendo anche la paginazione dei risultati.
Le Fetch API hanno una caratteristica peculiare: la Promise restituita invocando fetch()
è sempre posta nello stato resolved
. Ciò significa che per capire se la richiesta HTTP ha avuto successo dobbiamo interrogare la proprietà booleana ok
della risposta e sollevare un'eccezione nel caso in cui il suo valore fosse false
.
async function getData(url) {
try {
const request = await fetch(url);
if(!request.ok) {
throw new Error('Request error.');
}
return await request.json();
} catch(err) {
return null;
}
}
In questo caso avremo un valore effettivo in JSON solo se la richiesta iniziale ha avuto esito positivo.
Deleghiamo il reperimento dei dati con paginazione ad una funzione specifica:
async function getFacts(page = 1) {
const list = document.getElementById('facts-list');
const preloader = document.getElementById('preloader');
preloader.classList.remove('loaded');
try {
const url = `${API_ENDPOINT}?page=${page}`;
const facts = await getData(url);
if(!facts) {
throw new Error('Request error.');
}
const data = facts.data;
let contents = '';
for(const fact of data) {
contents += `<li>${fact.fact}</li>`;
}
list.innerHTML = contents;
createPaginationLinks(facts.prev_page_url, facts.next_page_url);
preloader.classList.add('loaded');
} catch(err) {
preloader.classList.add('loaded');
list.innerHTML = '<li class="error">No data to show.</li>';
}
}
I dati relativi alla paginazione di solito vengono restituiti come URL completo di query string contenente il numero di pagina. In questo caso prev_page_url
e next_page_url
conterranno gli URL della pagina precedente e successiva o null
se tali pagine non esistono.
Aggiungiamo quindi i pulsanti di navigazione delle pagine:
function createPaginationLinks(previous, next) {
const pagination = document.getElementById('pagination');
pagination.innerHTML = '';
if(previous) {
const previousBtn = document.createElement('button');
previousBtn.type = 'button';
previousBtn.dataset.page = previous;
previousBtn.innerText = 'Previous';
previousBtn.id = 'previous';
previousBtn.className = 'pagination-link';
pagination.appendChild(previousBtn);
}
if(next) {
const nextBtn = document.createElement('button');
nextBtn.type = 'button';
nextBtn.dataset.page = next;
nextBtn.innerText = 'Next';
nextBtn.id = 'next';
nextBtn.className = 'pagination-link';
pagination.appendChild(nextBtn);
}
}
Ciascun pulsante ha l'attributo di dati page
in cui viene salvato l'URL della paginazione. Per gestire il click su questi pulsanti dobbiamo far ricorso alla event delegation in quanto i pulsanti vengono aggiornati dinamicamente nel DOM ad ogni richiesta.
function handlePagination() {
document.addEventListener('click', evt => {
if(evt.target.classList.contains('pagination-link')) {
const button = evt.target;
const link = button.dataset.page;
const page = parseInt(link.split('?')[1].replace('page=', ''), 10);
getFacts(page);
}
});
}
Infine, inizializziamo il nostro codice:
document.addEventListener('DOMContentLoaded', () => {
handlePagination();
getFacts();
});
Demo
Conclusione
A livello teorico l'unico dettaglio tecnico potenzialmente problematico nell'uso delle Fetch API riguarda la gestione delle eccezioni HTTP. Il resto delle feature è sicuramente più intuitivo per l'uso che ne vogliamo fare.