Skip to content

Instantly share code, notes, and snippets.

@oilsinwater
Last active February 19, 2018 23:39
Show Gist options
  • Save oilsinwater/ff2f6162b5b739b47dd3d622e5c3d84a to your computer and use it in GitHub Desktop.
Save oilsinwater/ff2f6162b5b739b47dd3d622e5c3d84a to your computer and use it in GitHub Desktop.
Introduction to Fetch API

[TOC]

Fetch API

@(js study notes)[js, dev, web]

What is window.fetch

Modern replacement for XMLHttpRequest

  • Handles redirection, decodes common formant
  • Promise based(cleaner code)
  • Used by service workers, cache API, etc
  • Implements Cross Origin Resource Sharing (CORS*)

Want some JSON?

Below, we're fetching a JSON file named example.json, decoding it, and using the data. fetch returns a promise, so we use then to wait for a result. Once we have it, result includes a function for decoding the JSON. That also returns a promise, so we use then to wait for the decoder to finish. The final catch intercepts any errors that come up.

fetch('/examples/example.json')  
.then(function (response)) { 
  return response.json(); 
  }) //. then do something with that data
  .catch(function (error) { 
    console.log ('Fetch failed', error);
    });

Handling errors?

By default, fetch doesn't throw an exception if the server returns an error, but instead returns a normal response. The response.ok property will be true if the server returned 200, and false otherwise. You can always check response.status for the actual code. In this example, we're throwing an exception if the file can't be found. This is a best practice, and you should make a habit of checking response.ok early.

fetch('non-existent.json')
.then(function (response) {
  if (!response.ok) {
    throw response.statusText;
    }
    // use the response
    )};

CORS

The browser has strict rules about going outside your domain to fetch resources, so you'll likely get an error. It's not fetch's fault. The same thing can happen with XML HTTP request. Most developers resort to hacks to get around this, using JSONP or dynamic HTML to load content. But there's a better, standardized way called CORS. Enables fetch to retrieve resources from outside of your application's origin.

  • origin combination of scheme, host, and port. http://www.example.com has a scheme of HTTP, port 80, and the host is www.example.com. If any of these things change, say from HTTP to HTTPS, that's a different origin. There are exceptions to allow different origins

  • the single origin model

    • Browser enforced
      • Requests must match the page's scheme, hostname, and port
      • Exception: images, scripts, video/audio, embeds
      • Example: www.example requests JSON from www.json.com fails
  • CORS Implementations * Browser can request cross-origin access via CORS * Adds origin header on request * Server sends access-control-allow-origin if allowed

What if the server doesn't support CORS?

If the server doesn't support CORS, fetch will return an opaque response. The content is in there, but any attempt to read it cross-origin will throw an exception. Note that fetch uses CORS by default

// foo.com/main.js

fetch('https://bar.com/data.json', {
  mode: 'no-cors' // 'cors' by default
  })
  .then(function (response) {
    // response is opaque
    });

** Opaque response **

  • Returned from cross-origin requests to a server without CORS
    • Javascript can't use the content
    • Response can still be used in the page
    • Also can be cached by Cache API
{type: "opaque", url: "", status: 0, 
  ok: false, statusText: "", ...}

** Opaque example**

In the example below, we're forcing it to disable CORS. Why do this? Imagine you have an IFrame with the same origin as the response. You could pass the response object to it and it can use the result. This becomes important when you add offline support to an app and need to cache responses from different origins.

fetch('//google.com', {
  mode: 'no-cors'
  }).then(function (response) {
      console.log(response.type);  // "opaque"
      });

####** Can use GET method** We've been using get, but you can use any HTTP verb. Here, we're using the head method to get the size of a resource without actually loading the resource itself. The content-length field contains the size in bytes.

fetch('/example/video.mp4', {
  method: "HEAD"
  })
  .then(function (response) {
    var headers = response.headers:
    return headers.get('content-length');
    })

####** Make a POST request ** What if you want to send some data? You can use the post method to send it. This is an HTTP request with a body, so you need to supply that as body property. Remember that you are responsible for encoding the body properly and you should always encrypt any sensitive user data.

fetch('someurl/comment', {
  method: 'POST',
  body: 'title=hello&message=world'
  })

####** POST with FormData** We could post a form easily, using a FormData object. Just create a FormData object, passing it a DOM node, and the data is ready to use. This is much easier than individually compiling key value pairs.

var commentForm =
	document.getElementById('comment-form');

var form = new FormData(commentForm);

fetch('someurl/comment', {
  method: 'POST',
  body: form
  });

####Setting custom headers You can also set custom headers for fetch requests, and you can set headers for responses. Note that sending custom headers with cross-origin requests will cause your request to be preflighted. This means an options message will be sent to the other server first, and the browser will check for access control allow origin on return.

Note: Also, not all headers are modifiable. Some are guarded, such as origin, and writing will fail silently or with an error depending on the header.

var myHeaders = new Headers ({
  'Content-type': 'text/plain'
  });
fetch('someurl/comment', {
  method: 'POST',
  body: 'title=hello&message=world',
  headers: myHeaders
  }):

####** Trick for Reading Headers ** One last trick for using headers. If you don't always know what type to expect for a request, you can look at the content type header. Think of user-generated content or a server that's in the process of migrating from JSONP to straight JSON.

fetch(myRequest)
.then(function (response) {
  var headers = response.headers;
  var contentType = 
  headers.get('content-type');
  // Process based on content type
  });

Review the fetch API

This example gets an image, extracts the data, and appends it to the DOM. ReadResponseAsBlob and MakeImageNode are functions that we wrote.

fetch('/images/kitten.jpg')
.then(readResponseAsBlob)
.then(makeImageNode)
// .then append to DOM ...

** Grab an image **

The ReadResponseAsBlob function extracts the data from the response, but first, it throws an exception if the input response is not in the 200 range.

function readResponseAsBlob(response) {
  if (!response.ok) {
       throw Error(response.statusText);
     }
       return response.blob()
 }

** ...and make a DOM node **

We pass that data to MakeImageNode, this takes the image data and packages it as an image node to be added to the DOM. Here, we're using CreateObjectURL to generate a data URL with the image, and setting it as the source attribute in an image tag.

Note: The old way to do this was to pass the files you are to the source attribute and then capture the load and error events on the image tag. The fetch-based version is much cleaner and simpler. Now it's your turn.

function makeImageNode(imgBlob) {
  var myImage = document.createElement('img');
  var url = URL.createObjectURL(imgBlob);
  myImage.src = url;
  return myImage;
  }


----------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment