Dr. Greg Bernstein
Updated October 29th, 2020
Example Code FetchExamples.zip
Using Fetch (CSS-Tricks). Recommended, (May 2nd, 2017).
Response object (MDN). Need to know this to work with the response from fetch().
AJAX stands for Asynchronous JavaScript and XML. See Wikipedia AJAX.
With Ajax, Web applications can send data to and retrieve from a server asynchronously (in the background) without interfering with the display and behavior of the existing page.
All modern web applications that work with a server use some form of AJAX.
The XMLHttpRequest
was standardized in 2006. We will be using the modern version called fetch()
.
Note that fetch() is fairly widely supported caniuse and polyfills are available.
XML (eXtensible Markup Language) is a markup language that looks a lot like HTML but where you get to define the tags and attributes.
XML is significantly more complex and verbose than JSON and requires a correspondingly more complex parser to understand it.
Hence JSON is the primary data transfer format use with AJAX and not XML, but AJAJ (Asynchronous JavaScript and JSON) isn’t as catchy an acronym.
To try out the fetch()
functionality we need a server.
We will use the QuizServer.js Node.js application that I wrote.
API for the QuizServer.js
File QuizServer.js
const express = require('express');
const app = express();
app.use(express.static('public')); // For static assets
const questions = require('./questions.json');
app.get('/allQs', function (req, res) {
res.json({questions: questions});
}
)
app.post('/addQ', express.json(), function(req, res){
questions.push(req.body.q);
res.json(req.body.q);
})
Bundled with the FetchExamples.zip in the /public
directory
get examples, post examples, and error examples
from simpleGet1.html:
var infoP = document.getElementById("Info");
window.onload = function() {
var pButton = document.getElementById("TheButton");
pButton.addEventListener("click", getIt);
}
function getIt() {
// fetch returns a promise, must use "then"
fetch('/allQs').then(function(response) {
infoP.innerHTML = response;
console.log(response);
});
}
The body is left as a “readable stream”.
from simpleGet2.html:
var infoP = document.getElementById("Info");
var infoP2 = document.getElementById("Info2");
window.onload = function() {
var pButton = document.getElementById("TheButton");
pButton.addEventListener("click", getIt);
}
function getIt() {
fetch('/allQs').then(response => response.json())
.then(function(data) {
infoP.innerHTML = data;
// Had to escape the returned string, since
// questions had HTML tags.
infoP2.innerHTML = escapeHtml(JSON.stringify(data));
console.log(data);
});
}
We will use the “POST” method
We will be sending JSON so we need to indicate that in a HTTP header
We need to specify the request message body
From file sampleFetchPost.html
function postIt() {
fetch('/addQ', {
method: 'POST',
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({
q: q
})
}).then(function(response) {
console.log('Request status code: ', response.statusText, response.status, response.type);
});
}
Getting data back. From file sampleFetchPost2.html
function postIt() {
const addUrl = 'http://localhost:5555/addQ';
fetch('/addQ', {
method: 'POST',
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({
q: q
})
}).then(function(response) {
console.log('Request status code: ', response.statusText, response.status, response.type);
return response.json();
})
.then(function(data) {
infoP.innerHTML = escapeHtml(JSON.stringify(data));
console.log(data);
});
}
There are a lot of ways we can run into errors when fetching across a network:
Request times out, Non-existant server, Bad IP address…
Bad URL path (not found errors), Internal server errors…
And more…
Bad URL path, from file errorGet1
function getIt() {
// Bad URL path here
fetch('/allQss').then(response => response.json())
.then(function(data) {
infoP.innerHTML = data;
infoP2.innerHTML = escapeHtml(JSON.stringify(data));
console.log(data);
});
}
From file errorGet2.html
function getIt() {
// Bad URL path here
fetch('/allQss').then(function(response) {
if(response.ok) {
return response.json(); // a promise
} else {
let info = `Status code: ${response.status}, ${response.statusText}`;
console.log(response);
return Promise.reject(info); //rejected promise!
}
}) // Returns a promise
.then(function(data) {
infoP.innerHTML = data;
infoP2.innerHTML = escapeHtml(JSON.stringify(data));
console.log(data);
})
.catch(function(msg){
console.log("Something bad: " + msg);
infoP2.innerHTML = msg;
})
}
Note reporting of status code
From file errorGet3.html
function getIt() {
// Bad IP address here
fetch('http://127.200.121.101/allQs')
.then(function(response) {
if(response.ok) {
return response.json();
} else {
let info = `Status code: ${response.status}, ${response.statusText}`;
console.log(response);
return Promise.reject(info);
}
})
.then(function(data) {
infoP.innerHTML = data;
infoP2.innerHTML = escapeHtml(JSON.stringify(data));
console.log(data);
})
.catch(function(msg){
console.log("Something bad: " + msg);
infoP2.innerHTML = msg;
})
}
Reference MDN CORS
“A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.”
For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, the Fetch API follows the same-origin policy. This means that a web application using those APIs can only request resources from the same origin the application was loaded from, unless the response from other origins includes the right CORS headers.
Two websites serving JSON data:
https://cs651.grotto-networking.com/Tours Tours for an imaginary tour company.
https://windsurf.grotto-networking.com/data/logs/windEvents2019.json A years worth of sailing log data.
Can get to JSON data by directly navigating to the sites. Try it!
From ExternalFetch.html
HTML essentials
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>External Fetch Example</title>
<!-- Styling not shown -->
</head>
<body>
<main>
<!-- Introductory HTML removed -->
<section class="fetchResults">
<h2>Cooperating Site</h2>
<button id="Button1">Get JSON</button>
<p>JSON stringified information:</p>
<p id="Info1"></p>
</section>
<section class="fetchResults">
<h2>Non-Cooperating Site</h2>
<button id="Button2">Get JSON</button>
<p>JSON stringified information:</p>
<p id="Info2"></p>
</section>
</main>
<script><!-- See next slide --></script>
</body>
</html>
From ExternalFetch.html
JavaScript essentials
const el1 = 'Info1',
el2 = 'Info2',
url1 = 'https://cs651.grotto-networking.com/Tours',
url2 = 'https://windsurf.grotto-networking.com/data/logs/windEvents2019.json';
function getIt(url, el) {
fetch(url)
.then(response => response.json())
.then(function(data) {
let display = document.getElementById(el);
display.innerHTML = escapeHtml(JSON.stringify(data));
console.log(`Data from: ${url}:`);
console.log(data);})
.catch(function(err){
console.log(`Error from: ${url}:`);
console.log(err);});
}
let getIt1 = getIt.bind(null, url1, el1);
let getIt2 = getIt.bind(null, url2, el2);
function escapeHtml(str) { // A trick to escape HTML
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
window.onload = function() {
var pButton1 = document.getElementById("Button1");
pButton1.addEventListener("click", getIt1);
var pButton2 = document.getElementById("Button2");
pButton2.addEventListener("click", getIt2);
}
Successful CORS Fetch
Unsuccessful CORS Fetch
Only the server that owns the cross-origin resource can fix this issue and only if it doesn’t cause a security issue.
The page making the fetch has no control over this for security purposes.
// Pulls tour information out of a database and sends it
// as JSON. Note the added headers!
app.get('/tours', async function (req, res) {
// Use a "projection" so we don't send them the _id field.
let tours = await tourDB.find({}, {_id: 0});
// To allow CORS
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.json(tours);
});