JavaScript Async - The Fetch API
Written by Ian Elliot   
Monday, 16 October 2017
Article Index
JavaScript Async - The Fetch API
FormData, Get & Post

There are a number of new features in JavaScript that make good use of promises and hence async and await. The Fetch API is a replacement for the XMLHttpRequest function and perhaps the jQuery Ajax function. It also has a big role to play in the action of a ServiceWorker. If you are using modern JavaScript you probably should be using Fetch.

Now Available as a Book:

 JavaScript Async

cover

You can buy it from: Amazon

Contents

  1. Modern JavaScript (Book Only)
  2. Events,Standard & Custom
  3. The Callback
      extract - The Callback & The Controller
  4. Custom Async - setTimeout, sendMessage & yield
      extract - Custom Async
      extract - Avoiding State With Yield 
  5. Worker Threads
      extract - Basic Worker ***NEW
      extract - Advanced Worker Threads 
  6. Consuming Promises 
  7. Producing Promises
      extract - The Revealing Constructor Pattern
     
    extract - Returning Promises
     
    extract - Composing Promises
  8. The Dispatch Queue
      extract - Microtasks
  9. Async & Await
      extract -  Basic Async & Await
      extract -  DoEvents & Microtasks
  10. Fetch, Cache & Service Worker
      extract - Fetch  
      extract - Cache
     
    extract -  Service Workers

 

While the Cache API is primarily of use in a ServiceWorker, it is also generally available to JavaScript code. You can use it to store and retrieve resources and it is largely a replacement for the soon to be removed appCache.

The ServiceWorker is a modified form of the WebWorker designed to provide a life for your app when connectivity isn’t available. but it is so much more than this. It intercepts all of the traffic from the rest of your app and can completely control what is returned. In this sense it not only provides a way to allow your app to work when off-line. it also provides opportunities for modifying how the app updates when it is on-line.

Fetch, Cache and ServiceWorker are three key components in a new approach to web apps – the progressive web app or PWA. There are a number of libraries, perhaps most notably Angular CLI but you don’t have to adopt a library or framework to get the advantage of using ServiceWorker.

In this extract from Chapter 9 we look at the Fetch API. See the rest of the chapter for details of the Cache API, Service Workers and how it all fits together.

Basic Fetch

The Fetch API is a modern implementation of the XMLHttpRequest and it can be used to download almost any file the browser has access to and to send data to the server using Get or Post.

The basic idea is really simple. All you have to do is use the fetch function:

fetch(“URL”);

this performs a Get request for the URL specified and returns a promise that eventually resolves to a response object. The fetch function is available in the Window and WorkerGlobal contexts.  

Most HTTP errors are also returned as a resolved Promise and a Response object that specifies the error. The reject state is reserved for communications errors.

You could use the promise’s then method to specify what happens to the response but it is much simpler to use async and await:

async function getText(){
       var response=await fetch('myFile.txt');

For a simple file get this is almost all there is to using fetch. The response object returned has a set of methods and properties that allow you to discover the status of the request and retrieve the data.

For example the status property returns the HTTP status code – usually 200. As already mentioned HTTP errors such as 404 (no such page) are returned as resolved promises and you have to handle them as errors. You only get a rejected promise if there is something wrong that is more reasonably characterized as an exception. You can also retrieve the headers sent from the server using the headers property which returns a Headers object.

Notice that at this stage we only have the HTTP headers and status the data are still to be fetched across the network.

The Response.body gives you access to a readable stream. This allows you to read the data in a chunk at a time. This is useful when, for example, you are trying to work with something that is too big to fit in memory or when data is being continuously generated. The Streams API is another new feature that makes use of Promises. To read the Response in chunks you would use something like:

var reader=Response.body.getReader();

and following this each time you use the read method a Promise which resolves to the next chunk of the stream is returned:

var data=await reader.read();

The data is of the form

{value:chunk,done: boolean}

where value is the data and done is true if this is the last chunk of data.

Notice that a stream can only be read once unless it is recreated.

Streams are very low level compared to what most people want to do with retrieved resources. For this reason the Body object also implements a set of higher level stream readers. These return a Promise that resolves after the entire stream has been read to the data in a processed format.

Currently the supplied formatted readers are:

arrayBuffer
blob
formData
json

and

 text

and each returns a promise which resolves to the type of data you have selected.

One subtle point is that you can only retrieve a response’s data once. This is obvious if you keep in mind that the methods that retrieve the data are stream readers – you can only read a stream to the end once. However it can cause problems if you mistake these methods as simply providing format conversion.

For example:

async function getText(){
 var response=await fetch('myFile.txt');
 console.log(response.status);
 console.log(await response.text());
}

This retrieves the data in the file as text. Once the response body has been retrieved or “used” you cannot repeat the operation.

That is:

async function getText(){
  var response=await fetch('myFile.txt');
  console.log(response.status);
  console.log(await response.text());
  console.log(await response.text()); }

Throws an exeception:

Uncaught (in promise) TypeError: Already read

You can check to see if the body has been used via the bodyUsed property of either the request or the response. If you do want to access the data in more than one format then you have to make use of the clone method – see later.

Request Object

Things are only a little more complicated when you want to do something more than just a get. You can specify a second parameter, the init object, in the call which controls the type of request made.

The init object has lots of properties but the most important are:

 

  • method – the request method e.g. GET, POST, PUT etc.

  • headers – a header object

  • body – the body of the request which is sent to the server

 

You can find the full specification in the documentation.

If you want to repeatedly fetch the same resource it is better to create a Request object which has all of the properties of the init object plus a url property. You can pass the Request object to fetch in place of the URL parameter and it acts as the init object as well.

So the previous fetch could be implemented as:

async function getText(){
  var request=new Request(’myFile.txt’,
                             { method:’GET’});
  var response=await fetch(request);

Notice that you can reuse a request object even if you have streamed the body data of its associated Response. However as already commented you cannot reuse a Response object after you have read its body data.

You can obtain a duplicate Request or Response object using the clone method. This can be useful if you aren’t sure that the response will be valid. For example to first check to see if the response if valid json we could use:

var response = await fetch(request);
var res2 = response.clone();
try{
     console.log(await res2.json());
   }
catch (e) {
     console.log(await response.text());
}

If the response isn’t valid json it is displayed as text. Notice you have to clone the response before trying to retrieve the body. You cannot clone a stream that has been read.

You can use a fluent style to make this look neater:

var response = await fetch(request);
 try{
      console.log(await response.clone().json());
    }
 catch (e) {
      console.log(await response.text());
    }

The clone method introduces a buffer into the equation and the data is streamed into a local butter from where it can be accessed a second time. This has the side effect of keeping the data in memory until all of the copies are read or disposed of.

 



Last Updated ( Friday, 08 February 2019 )