AJAX- Fetch

A cleaner and newer interface, the Fetch API provides a more powerful and flexible feature set than XMLHttpRequest.

Note that you can cache the Fetch Request and Response using the Web Cache API to speed up repetitive loading.



Requesting JSON file

A basic fetch request() takes one parameter and returns a promise.
RESETRUNFULL
<!DOCTYPE><html>
<body>
   <pre></pre>
   <script>
      fetch('/shared/students.json')
        .then(response => response.json())
        .then(data =>  document.querySelector("pre").appendChild(document.createTextNode(JSON.stringify(data,null,3))));
   </script>
</body></html>


Second Parameter
You can pass in a second parameter to fetch() to precisely control how the request is sent.

The Body mixin defines the following methods to extract a body (implemented by both Request and Response):

  • arrayBuffer()
  • blob()
  • json()
  • text()
  • formData()
This makes usage of non-textual data much easier than it was with XHR.


RESETRUNFULL
<?php  // /shared/answer.php   ... (not meant to be changed in this demo)
$_POST = json_decode(file_get_contents("php://input"), true);
if      ($_POST["answer"]==33) echo json_encode((object)array("result"=>"correct!"));
else if ($_POST["answer"] <33) echo json_encode((object)array("result"=>"too small"));
else                           echo json_encode((object)array("result"=>"too big"));
?>

<!DOCTYPE><html>
<body>
   <pre></pre>
   <script>
      async function postData(url = '', data = {}) {  // Default options are marked with *
         const response = await fetch(url, {
             method: 'POST',                // *GET, POST, PUT, DELETE, etc.
             mode: 'cors',                  // no-cors, *cors, same-origin
             cache: 'no-cache',             // *default, no-cache, reload, force-cache, only-if-cached
             credentials: 'same-origin',    // include, *same-origin, omit
             headers: {
                 'Content-Type': 'application/json',   // 'Content-Type': 'application/x-www-form-urlencoded'
             },
             redirect: 'follow',            // manual, *follow, error
             referrerPolicy: 'no-referrer',
                  /* no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url */
             body: JSON.stringify(data)     // body data type must match "Content-Type" header
         });
         return response.json();            // parses JSON response into native JavaScript objects
      }
      postData('/shared/answer.php', { answer: 42 })
         .then(r => document.querySelector("pre").appendChild(document.createTextNode(r.result)));
  </script>
</body></html>


FormData

You can extract all data from a form directly into FormData. Note that FormData must be fetched over the 'POST' method.
RESETRUNFULL
<?php    //  /shared/answer2.php
if      ($_POST["answer"]==33) echo "correct!";
else if ($_POST["answer"] <33) echo "too small";
else                           echo "too big";
?>

<!DOCTYPE><html>
<body>
    <form>
      <input name="answer" type="number" value="50"/>
    </form>
    <button type="button" onclick="submit()">GUESS</button>
    <p></p>
    <script>
       function submit(){
          fetch("/shared/answer2.php", {method:"POST", body:(new FormData(document.querySelector('form')))})
          .then(r=>r.text())
          .then(r=>{
              document.querySelector("p").innerHTML = r;
          });
       }
    </script>
</body></html>
Files can be uploaded using an HTML <input type="file" multiple /> input element, FormData() and fetch().
RESETRUNFULL
<!DOCTYPE><html>
<body>
    <script>
      function upload(){
        const formData = new FormData();
        formData.append('title', 'My Vegas Vacation');
        const photos =  document.querySelector('input[type="file"][multiple]');
        for (let i = 0; i < photos.files.length; i++) formData.append('photos', photos.files[i]);
        fetch('/dummy/upload_photos', { method: 'POST', body: formData})     // demo only
        .then(response => response.json())
        .then(result => { console.log('Success:', result);})
        .catch(error => { console.error('Error:', error);});
      }
   </script>
   <input type="file" multiple/>
   <button onclick="upload()" type="button">UPLOAD</button>
</body></html>


Asynchronous Iteration

The chunks that are read from a response are not broken neatly at line boundaries and are Uint8Arrays, not strings. The following example processes a text file line-by-line.
RESETRUNFULL
<!DOCTYPE><html>
<body>
    <script>
       async function* makeTextFileLineIterator(fileURL) {
          const utf8Decoder = new TextDecoder('utf-8');
          const response = await fetch(fileURL);
          let reader = response.body.getReader()
          let { value, done } = await reader.read();
          value = value ? utf8Decoder.decode(value) : '';
          const re = /\n|\r|\r\n/gm;
          let startIndex = 0;
          let result;
          for (;;) {
             let result = re.exec(value);
             if (!result) {
                if (done) break;
                let remainder = value.substr(startIndex);
                let { value, done } = await reader.read();
                value = remainder + (value ? utf8Decoder.decode(value) : '');
                startIndex = re.lastIndex = 0;
                continue;
             }
             result = value.substring(startIndex, result.index);
             if (result.trim()!="") yield result;
             startIndex = re.lastIndex;
          }
          if (startIndex < value.length) {    // last line didn't end in a newline char
             yield value.substr(startIndex);
          }
       }
       async function run() {
          for await (let line of makeTextFileLineIterator("/shared/wishes_for_sons.txt")) alert(line);
       }
   </script>
   <button onclick="run()" type="button">RECITE: Wishes for Sons</button>
</body></html>


Checking for Fetch Success

The Promise returned from fetch() won't reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything (eg. CORS misconfiguration) prevented the request from completing.
RESETRUNFULL
<!DOCTYPE><html>
<body>
    <script>
       function run(){
          fetch('/shared/flowers.jpg')
            .then(response => {
                alert("promise resolved normally");
                if (!response.ok) throw new Error('Network response was not ok');
                return response.blob();
             })
            .then(myBlob => { myImage.src = URL.createObjectURL(myBlob); })
            .catch(error => {
               alert('There has been a problem with your fetch operation:'+ error);
            });
       }
   </script>
   <button onclick="run()" type="button">FETCH</button>
</body></html>


The Request and Headers Objects

Request and Response bodies are one-use only. You can create a copy of the Request before the body is read.
RESETRUNFULL
<!DOCTYPE><html>
<body>
    <img/>
    <script>
      const myHeaders = new Headers();
      const myInit = {
           method: 'GET',
           headers: myHeaders,
           mode: 'cors',
           cache: 'default'};
      const myRequest = new Request('/shared/testing.gif', myInit);
      const anotherRequest = new Request(myRequest, myInit);  // The fetch() method's parameters are identical to those of the Request() constructor.
      fetch(anotherRequest)
        .then(response => response.blob())
        .then(myBlob => {
           document.querySelector("img").src = URL.createObjectURL(myBlob);
        });
    </script>
</body></html>
You can pass in your Headers object. A good use case for headers is checking whether the content type is correct before you process it further.
const content = 'Hello World';
const myHeaders = new Headers();
myHeaders.append('Content-Type', 'text/plain');
myHeaders.append('Content-Length', content.length.toString());
myHeaders.append('X-Custom-Header', 'ProcessThisImmediately');

const myHeaders = new Headers({
  'Content-Type': 'text/plain',
  'Content-Length': content.length.toString(),
  'X-Custom-Header': 'ProcessThisImmediately'
});

console.log(myHeaders.has('Content-Type')); // true
console.log(myHeaders.has('Set-Cookie')); // false
myHeaders.set('Content-Type', 'text/html');
myHeaders.append('X-Custom-Header', 'AnotherValue');
console.log(myHeaders.get('Content-Length')); // 11
console.log(myHeaders.get('X-Custom-Header')); // ['ProcessThisImmediately', 'AnotherValue']
myHeaders.delete('X-Custom-Header');
console.log(myHeaders.get('X-Custom-Header')); // [ ]

const myResponse = Response.error();
try {
  myResponse.headers.set('Origin', 'http://mybank.com');
} catch (e) {
  console.log('Cannot pretend to be a bank!');
}

fetch(myRequest)
  .then(response => {
     const contentType = response.headers.get('content-type');
     if (!contentType || !contentType.includes('application/json')) throw new TypeError("Oops, we haven't got JSON!");
     return response.json();
  })
  .then(data => {
      /* process your data further */
  })
  .catch(error => console.error(error));


Aborting

The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
To observe how aborting works below, go to full-screen mode, press F12 and navigate to 'Network'.

RESETRUNFULL
<!DOCTYPE><html>
<body>
    <button class="download" type="button">DOWNLOAD</button>
    <button class="abort" type="button">ABORT</button>
    <script>
      var controller = new AbortController();
      var signal = controller.signal;
      var downloadBtn = document.querySelector('.download');
      var abortBtn = document.querySelector('.abort');
      downloadBtn.addEventListener('click', fetchVideo);
      abortBtn.addEventListener('click', function() {
        controller.abort();
        alert('download aborted');
      });
      function fetchVideo() {
         alert("download about to start");
         fetch("/shared/sample.mp4", {signal}).then(function(response) {
            console.log(response);
         })
         .catch(function(e) {
             reports.textContent = 'Download error: ' + e.message;
         });
      }
   </script>
</body></html>

The Fetch API requires Set-Cookie headers to be stripped before returning a Response object from fetch().