Another very common task in modern websites and applications is retrieving individual data items from the server to update sections of a webpage without having to load an entire new page. This seemingly small detail has had a huge impact on the performance and behavior of sites, so in this article, we'll explain the concept and look at technologies that make it possible, such as XMLHttpRequest and the Fetch API.
| Prerequisites: | JavaScript basics (see first steps , building blocks , JavaScript objects ), the basics of Client-side APIs |
|---|---|
| Objective: | To learn how to fetch data from the server and use it to update the contents of a web page. |
Originally page loading on the web was simple — you'd send a request for a website to a server, and as long as nothing went wrong, the assets that made the web page would be downloaded and displayed on your computer.
The trouble with this model is that whenever you want to update any part of the page, for example, to display a new set of products or load a new page, you've got to load the entire page again. This is extremely wasteful and results in a poor user experience, especially as pages get larger and more complex.
This led to the creation of technologies that allow web pages to request small chunks of data (such as HTML , XML , JSON , or plain text) and display them only when needed, helping to solve the problem described above.
This is achieved by using APIs like
XMLHttpRequest
or — more recently — the
抓取 API
. These technologies allow web pages to directly handle making
HTTP
requests for specific resources available on a server and formatting the resulting data as needed before it is displayed.
注意:
In the early days, this general technique was known as
异步
JavaScript and XML (
Ajax
), because it tended to use
XMLHttpRequest
to request XML data. This is normally not the case these days (you'd be more likely to use
XMLHttpRequest
or Fetch to request JSON), but the result is still the same, and the term "Ajax" is still often used to describe the technique.
The Ajax model involves using a web API as a proxy to more intelligently request data rather than just having the browser reload the entire page. Let's think about the significance of this:
This is a really good thing because:
To speed things up even further, some sites also store assets and data on the user's computer when they are first requested, meaning that on subsequent visits they use the local versions instead of downloading fresh copies every time the page is first loaded. The content is only reloaded from the server when it has been updated.
Let's look at how such a request is handled, using both
XMLHttpRequest
and
Fetch
. For these examples, we'll request data out of a few different text files and use them to populate a content area.
This series of files will act as our fake database; in a real application, we'd be more likely to use a server-side language like PHP, Python, or Node to request our data from a database. Here, however, we want to keep it simple and concentrate on the client-side part of this.
XMLHttpRequest
(which is frequently abbreviated to XHR) is a fairly old technology now — it was invented by Microsoft in the late '90s, and has been standardized across browsers for quite a long time.
<script>
element, add the following code. This stores a reference to the
<select>
and
<pre>
elements in constants and defines an
onchange
event handler function so that when the select's value is changed, its value is passed to an invoked function
updateDisplay()
as a parameter.
const verseChoose = document.querySelector('select');
const poemDisplay = document.querySelector('pre');
verseChoose.onchange = function() {
const verse = verseChoose.value;
updateDisplay(verse);
};
updateDisplay()
function. First of all, put the following beneath your previous code block — this is the empty shell of the function. Note: Steps 4 - 9 will all be performed
在
this function.
function updateDisplay(verse) {
}
<select>
element at any time is the same as the text inside the selected
<option>
(unless you specify a different value in a value attribute) — so for example "Verse 1". The corresponding verse text file is "verse1.txt", and is in the same directory as the HTML file, therefore just the file name will do.
However, web servers tend to be case sensitive, and the file name doesn't have a space in it. To convert "Verse 1" to "verse1.txt" we need to convert the V to lower case, remove the space, and add .txt on the end. This can be done with
replace()
,
toLowerCase()
, and simple
string concatenation
. Add the following lines inside your
updateDisplay()
函数:
verse = verse.replace(" ", "");
verse = verse.toLowerCase();
let url = verse + '.txt';
XMLHttpRequest()
constructor. You can call this object anything you like, but we'll call it
request
to keep things simple. Add the following below your previous lines inside your
updateDisplay()
函数:
let request = new XMLHttpRequest();
open()
method to specify what
HTTP 请求方法
to use to request the resource from the network, and what its URL is. We'll just use the
GET
method here and set the URL as our
url
variable. Add this below your previous line:
request.open('GET', url);
responseType
property — as
text
. This isn't strictly necessary here — XHR returns text by default — but it is a good idea to get into the habit of setting this in case you want to fetch other types of data in the future. Add this next:
request.responseType = 'text';
onload
event handler — this is run when the
load
event fires (when the response has returned). When this has occurred, the response data will be available in the
response
property of the XHR request object.
Add the following below your last addition. You'll see that inside the
onload
event handler we are setting the
textContent
的
poemDisplay
(
<pre>
element) to the value of the
request.response
特性。
request.onload = function() {
poemDisplay.textContent = request.response;
};
send()
method. Add the following below your previous addition to complete the function. This line should rest just above the closing curly brace of your
updateDisplay()
函数。
request.send();
</script>
tag) to load verse 1 by default, and make sure the
<select>
element always shows the correct value:
updateDisplay('Verse 1');
verseChoose.value = 'Verse 1';
Modern browsers will not run XHR requests if you just run the example from a local file. This is because of security restrictions (for more on web security, read Website security ).
To get around this, we need to test the example by running it through a local web server. To find out how to do this, read How do you set up a local testing server?
The Fetch API is basically a modern replacement for XHR; it was introduced in browsers recently to make asynchronous HTTP requests easier to do in JavaScript, both for developers and other APIs that build on top of Fetch.
Let's convert the last example to use Fetch instead.
updateDisplay()
function, find the XHR code:
let request = new XMLHttpRequest();
request.open('GET', url);
request.responseType = 'text';
request.onload = function() {
poemDisplay.textContent = request.response;
};
request.send();
fetch(url).then(function(response) {
response.text().then(function(text) {
poemDisplay.textContent = text;
});
});
First of all, we invoke the
fetch()
method, passing it the URL of the resource we want to fetch. This is the modern equivalent of
request.open()
in XHR, plus you don't need any equivalent to
.send()
.
After that, you can see the
.then()
method chained onto the end of
fetch()
— this method is a part of
Promises
, a modern JavaScript feature for performing asynchronous operations.
fetch()
返回
promise
, which resolves to the response sent back from the server — we use
.then()
to run some follow-up code after the promise resolves, which is the function we've defined inside it. This is the equivalent of the
onload
event handler in the XHR version.
This function is automatically given the response from the server as a parameter when the
fetch()
promise resolves. Inside the function we grab the response and run its
text()
method, which basically returns the response as raw text. This is the equivalent of
request.responseType = 'text'
in the XHR version.
You'll see that
text()
also returns a promise, so we chain another
.then()
onto it, inside of which we define a function to receive the raw text that the
text()
promise resolves to.
Inside the inner promise's function, we do much the same as we did in the XHR version — set the
<pre>
element's text content to the text value.
Promises are a bit confusing the first time you meet them, but don't worry too much about this for now. You'll get used to them after a while, especially as you learn more about modern JavaScript APIs — most of the newer ones are heavily based on promises.
Let's look at the promise structure from above again to see if we can make some more sense of it:
fetch(url).then(function(response) {
response.text().then(function(text) {
poemDisplay.textContent = text;
});
});
The first line is saying "fetch the resource located at URL" (
fetch(url)
) and "then run the specified function when the promise resolves" (
.then(function() { ... })
). "Resolve" means "finish performing the specified operation at some point in the future". The specified operation, in this case, is to fetch a resource from a specified URL (using an HTTP request), and return the response for us to do something with.
Effectively, the function passed into
then()
is a chunk of code that won't run immediately. Instead, it will run at some point in the future when the response has been returned. Note that you could also choose to store your promise in a variable and chain
.then()
onto that instead. The code below would do the same thing:
let myFetch = fetch(url);
myFetch.then(function(response) {
response.text().then(function(text) {
poemDisplay.textContent = text;
});
});
由于
fetch()
method returns a promise that resolves to the HTTP response, any function you define inside a
.then()
chained onto the end of it will automatically be given the response as a parameter. You can call the parameter anything you like — the below example would still work:
fetch(url).then(function(dogBiscuits) {
dogBiscuits.text().then(function(text) {
poemDisplay.textContent = text;
});
});
But it makes more sense to call the parameter something that describes its contents.
Now let's focus just on the function:
function(response) {
response.text().then(function(text) {
poemDisplay.textContent = text;
});
}
The response object has a method
text()
that takes the raw data contained in the response body and turns it into plain text — the format we want it in. It also returns a promise (which resolves to the resulting text string), so here we use another
.then()
, inside of which we define another function that dictates what we want to do with that text string. We are just setting the
textContent
property of our poem's
<pre>
element to equal the text string, so this works out pretty simple.
It is also worth noting that you can directly chain multiple promise blocks (
.then()
blocks, but there are other types too) onto the end of one another, passing the result of each block to the next block as you travel down the chain. This makes promises very powerful.
The following block does the same thing as our original example, but is written in a different style:
fetch(url).then(function(response) {
return response.text()
}).then(function(text) {
poemDisplay.textContent = text;
});
Many developers like this style better, as it is flatter and arguably easier to read for longer promise chains — each subsequent promise comes after the previous one, rather than being inside the previous one (which can get unwieldy). The only other difference is that we've had to include a
return
statement in front of
response.text()
, to get it to pass its result on to the next link in the chain.
This really depends on what project you are working on. XHR has been around for a long time now and has very good cross-browser support. Fetch and Promises, on the other hand, are a more recent addition to the web platform, although they're supported well across the browser landscape, with the exception of Internet Explorer.
If you need to support older browsers, then an XHR solution might be preferable. If however you are working on a more progressive project and aren't as worried about older browsers, then Fetch could be a good choice.
You should really learn both — Fetch will become more popular as Internet Explorer declines in usage (IE is no longer being developed, in favor of Microsoft's new Edge browser), but you might need XHR for a while yet.
To round off the article, we'll look at a slightly more complex example that shows some more interesting uses of Fetch. We have created a sample site called The Can Store — it's a fictional supermarket that only sells canned goods. You can find this example live on GitHub ,和 see the source code .
By default, the site displays all the products, but you can use the form controls in the left hand column to filter them by category, or search term, or both.
There is quite a lot of complex code that deals with filtering the products by category and search terms, manipulating strings so the data displays correctly in the UI, etc. We won't discuss all of it in the article, but you can find extensive comments in the code (see can-script.js ).
We will however explain the Fetch code.
The first block that uses Fetch can be found at the start of the JavaScript:
fetch('products.json').then(function(response) {
return response.json();
}).then(function(json) {
let products = json;
initialize(products);
}).catch(function(err) {
console.log('Fetch problem: ' + err.message);
});
fetch()
function returns a promise. If this completes successfully, the function inside the first
.then()
block contains the
response
returned from the network.
Inside this function we run
json()
on the response, not
text()
, as we want to return our response as structured JSON data, not plain text.
Next, we chain another
.then()
onto the end of our first one, the success function that contains the
json
returned from the
response.json()
promise. We set this to be the value of the
products
variable, then run
initialize(products)
, which starts the process of displaying all the products in the user interface.
To handle errors, we chain a
.catch()
block onto the end of the chain. This runs if the promise fails for some reason. Inside it, we include a function that is passed as a parameter, an
error
object. This
error
object can be used to report the nature of the error that has occurred, in this case we do it with a simple
console.log()
.
However, a complete website would handle this error more gracefully by displaying a message on the user's screen and perhaps offering options to remedy the situation, but we don't need anything more than a simple
console.log()
.
You can test the fail case yourself:
localhost:8000
) and look in your browser developer console. You'll see a message similar to "Network request for produc.json failed with response 404: File not found".
The second Fetch block can be found inside the
fetchBlob()
函数:
fetch(url).then(function(response) {
return response.blob();
}).then(function(blob) {
// Convert the blob to an object URL — this is basically a temporary internal URL
// that points to an object stored inside the browser
let objectURL = URL.createObjectURL(blob);
// invoke showProduct
showProduct(objectURL, product);
});
This works in much the same way as the previous one, except that instead of using
json()
, we use
blob()
. In this case we want to return our response as an image file, and the data format we use for that is
Blob
(the term is an abbreviation of "Binary Large Object" and can basically be used to represent large file-like objects, such as images or video files).
Once we've successfully received our blob, we create an object URL out of it using
createObjectURL()
. This returns a temporary internal URL that points to an object referenced inside the browser. These are not very readable, but you can see what one looks like by opening up the Can Store app, Ctrl-/Right-clicking on an image, and selecting the "View image" option (which might vary slightly depending on what browser you are using). The object URL will be visible inside the address bar, and should be something like this:
blob:http://localhost:7800/9b75250e-5279-e249-884f-d03eb1fd84f4
We'd like you to try converting the Fetch version of the app to use XHR as a useful bit of practice. Take a copy of the ZIP file , and try modifying the JavaScript as appropriate.
Some helpful hints:
XMLHttpRequest
reference material useful.
request.response
后于
load
event has fired, not in a promise
then()
.
response.ok
in XHR is to check whether
request.status
is equal to 200, or if
request.readyState
is equal to 4.
request
(XHR) object, not the
response
对象。
注意: If you have trouble with this, feel free to check your code against the finished version on GitHub ( see the source here , and also see it running live ).
This article shows how to start working with both XHR and Fetch to fetch data from the server.
There are however a lot of different subjects discussed in this article, which has only really scratched the surface. For a lot more detail on these subjects, try the following articles:
最后修改: , 由 MDN 贡献者