PHP Promise e Fetch
Introdución
- Unha promise ven ser unha petición que realizamos de forma asíncrona a un servidor e que permite que o código do cliente continúe a súa execución.
- Una promesa ten tres estados:
- Resolved: Unha promesa normalmente vai solicitar unha información a un servidor. Cando dispón de dita información pasa ao estado resolved.
- Rejected: Cando a promesa non devolve un valor ou cando se produce unha excepción debido a que o valor devolto non ten o formato correcto.
- Pending: Cando xa iniciamos a petición e aínda non tivemos resposta.
- Unha promesa pode pasar do estado pending a resolved ou rejected e non pode ter transaccións entre os estados.
- Fetch tamén realiza unha operación asíncrona e devolve unha promesa xa preparada para o seu uso. Ten asociados tres métodos:
- .then(): Onde se reciben os datos.
- .catch(): Se se produce algunha excepción
- .finally(): Un código que podemos executar tanto se foi ben (then) como se foi mal (catch) a chamada a obtención de datos de forma asíncrona.
- Se os dous fan o mesmo, en que se diferencian ?
- Digamos que promise, que é parte da linguaxe JavaScript, é mais de 'proposito xeral'. Permite facer calquera tipo de petición asíncronas e permite executar múltiples operacións asíncronas e executalas en orde, unha despois de outra.
- Fetch pola súa parte, é unha función da API Fetch que está orientada a ser empregada para facer peticións de rede e recibir información de ditas solicitudes (por exemplo empregando HTTP Requests).
- Nos imos ver fetch xa que o imos empregar para realizar peticións a API Rest e obter os resultados.
Preparación
- Aínda que se pode probar dende calquera proxecto, eu parto de que tedes un proxecto Laravel 10 có seguinte:
- Arquivo: /routes/web/web.php
Route::get('/probas', function () { return view('probas'); })->name('probas');
- Fixádevos que a ruta vai directamente á vista e non pasa por un controlador. Isto é debido a que os datos que vou amosar os vou cargar dende o cliente empregando fetch. Isto non quita que nos vosos proxectos pasedes por un controlador para facer unha xestión ou recuperar datos dunha base de datos.
- Arquivo: /resources/views/principal.blade.php
<html> <head> <meta charset="UTF-8"> <link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet" /> <script src="{{ asset('js/bootstrap.min.js') }}"></script> </head> <body> <nav class="navbar navbar-expand-lg bg-primary" data-bs-theme="dark"> <div class="container-fluid"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarColor01" aria-controls="navbarColor01" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarColor01"> <ul class="navbar-nav me-auto"> <li class="nav-item"> <a class="nav-link" href="{{route('probas')}}">Probas</a> </li> </ul> </div> </div> </nav> @yield('content') @yield('antesdefinbody') </body> </html>
- Fixarse que creo dúas seccións:
- 'content' onde irán os contidos da páxina
- 'antesdefinbody': onde irán os arquivos javascript que farán o fetch dos datos.
- Parto de que tedes descargados os arquivos necesarios no cartafol /public/ para facer uso de bootstrap: https://getbootstrap.com/docs/4.0/getting-started/download/ (podedes empregar un CDN)
- Arquivo: /resource/views/probas.blade.php
@extends('principal') @section('content') <h3>Probas</h3> <div class="container text-center mt-5"> <input type="button" value="CARGAR DATOS DE INTERNET" onclick="javascript:cargarProbas()"/> <h5>Cargando datos dende internet</h5> <div id='idProbaImaxe'></div> </div> @endsection @section('antesdefinbody') <script src="{{asset('js/cargarProba1.js')}}"></script> @endsection
- Fixarse como na sección 'antesdefinbody' cargo os arquivos javascript. Isto poderíamos facelo localmente pero desta forma, a nivel de mantemento e claridade queda mellor.
- Arquivo: /public/js/cargarProba1.js
function cargarProbas(){ }
- Aquí irá o código javascript que iremos poñendo en cada proba que fagamos.
Funcionamento
- Máis información en: https://developer.mozilla.org/es/docs/Web/API/Fetch_API/Using_Fetch
- IMPORTANTE: Tedes que ter claro que este código se executa no cliente (no navegador). Polo tanto a información se obtén directamente da fonte sen necesidade de volver a cargar a páxina, como fixemos ata o de agora. Isto ten vantaxes e desvantaxes.
- Vantaxes:
- Liberade carga de traballo do servidor, sempre que a información a recuperar se atope noutro servidor diferente.
- Non se ten que volver a cargar a páxina enteira, soamente aquelas partes nas que queirades actualizar a información.
- Latencia de carga menor xa que accedes directamente á información sen pasar polo servidor.
- Desvantaxes:
- Non podedes empregar métodos de validación para obter a información xa que os token's estarían visibles para todo o mundo.
- Política do Mesmo Orixen (CORS): Restriccións de CORS se o servizo externo non permite explicitamente solicitudes dende o teu dominio.
- Xestión de Erros: Debes manexar os posibles erros de rede e os casos no que o servizo externo estea inaccesible.
- Vantaxes:
- O funcionamento é moi sinxelo.
- Recordar que isto é javascript, e polo tanto o código terá que ir entre etiquetas <script> </script> ou ben poñer o código nun arquivo separado e facer un <script src="arquivo.js"></script>
- Imos cargar uns datos de Internet empregando unha API Rest aberta. Unha url de exemplo podería ser esta: https://jsonplaceholder.typicode.com/posts/1
- Retorna 4 campos. Imos visualizar na consola o que nos devolve.
- Imos facelo empregando o proxecto de Laravel anterior.
- Arquivo: /public/js/cargarProba1.js
function cargarProba1(){ const url = 'https://jsonplaceholder.typicode.com/posts/1'; fetch(url) .then(function(response) { if(response.ok){ console.log(response); } else{ console.log("Resposta de rede OK pero resposta HTTP no ok") } } ) .catch(function(error) { console.error('Erro ao cargar:', error); }); }
- Arquivo: /resources/views/probas.blade.php
@extends('principal') @section('content') <h3>Probas</h3> <div class="container text-center mt-5"> <input type="button" value="CARGAR DATOS DE INTERNET" onclick="javascript:cargarProba1()"/> <h5>Cargando datos dende internet</h5> <div id='idProba1'></div> </div> @endsection @section('antesdefinbody') <script src="{{asset('js/cargarProbas.js')}}"></script> @endsection
- Temos que ter o proxecto laravel en funcionamento coa orde: php artisan serve
- Isto fai que o servidor web local estea escoitando no porto 8000. Cargamos páxina de probas poñendo no navegador: http://localhost:8000/probas
- Agora imos facer algunha mellora ao código anterior.
- Como vemos, dentro do then, imos a executar unha función anónima que recibe como parámetro un obxecto da clase Response, que chamamos response: .then(function(response) {}
- Nota: Lembrar recargar a páxina para que cargue o novo código.
- Iso é equivalente a poñer isto:
function cargarProba1(){ const url = 'https://jsonplaceholder.typicode.com/posts/1'; fetch(url) .then(response => { if(response.ok){ console.log(response); } else{ console.log("Resposta de rede OK pero resposta HTTP no ok") } } ) .catch(error => { console.error('Erro ao cargar:', error); }); }
- Agora imos avanzar un pouco máis.
- A nos o que nos interesa é o corpo da resposta e non o obxecto response que engloba a resposta e outra información, como o estado da resposta (propiedade status).
- Para obtela, imos convertir a resposta en un obxecto javascript o array de javascript (dependendo do tipo de resposta).
- Nota: Lembrar recargar a páxina para que cargue o novo código.
function cargarProba1(){ const url = 'https://jsonplaceholder.typicode.com/posts/1'; fetch(url) .then(response => { if(response.ok){ return response.json(); } else{ console.log("Resposta de rede OK pero resposta HTTP no ok") } } ) .then(datos => { console.log(datos); console.log('TITULO:' + datos.title); console.log(`body:${datos.body}`); } ) .catch(error => { console.error('Erro ao cargar:', error); }); }
- Fixarse o que estamos a facer neste código. Unha vez recuperamos o obxecto response, facemos un return de response.json().
- O que vai facer .json() é extraer o corpo da mensaxe e pasalo a un obxecto javascript. Isto o vai facer retornando unha promesa. Isto quere dicir que para obter o corpo o vai facer de forma asíncrona e agora por tanto, necesitamos facer un 'then' para obter a resposta (segundo then da liña 14).
- Ao igual que có fetch, o resultado (then) vai vir nunha variable que chamo 'datos' e amoso dito dato en consola.
- Fixarse como para obter un dato concreto poñemos punto e o nome do campo. Podemos empregar o máis para concatenar ou a comilla orientada cara a dereita e empregar dentro de ditas comillas a formato: ${variable}
- Agora o que necesitamos é visualizar dito contido na páxina HTML. Para iso imos obter un obxecto que vai representar o div con id='idProba1' e imos engadirlle contido dinamicamente.
- Nota: Lembrar recargar a páxina para que cargue o novo código.
function cargarProba1(){ const url = 'https://jsonplaceholder.typicode.com/posts/1'; fetch(url) .then(response => { if(response.ok){ return response.json(); } else{ console.log("Resposta de rede OK pero resposta HTTP no ok") } } ) .then(datos => { const contenedor = document.getElementById('idProba1'); contenedor.innerHTML=`TITULO: <strong>${datos.title}</strong>`; } ) .catch(error => { console.error('Erro ao cargar:', error); }); }
- Liña 15: Obtemos nunha constante o div en forma de obxecto javascript.
- Liña 16: Cambiamos a propiedade innerHTML xa que imos engadir código html (strong neste caso) ao seu contido. Se fora soamente texto poderíamos empregar innerText
- Pois isto é a base de calquera petición que fagades empregando fetch.
- Nos imos empregalo para obter datos empregando GET e peticións a unha API RestFul aberta que non necesite token de acceso, pero poderíamos empregalo para moito máis.
-- Ángel D. Fernández González -- (2024).