CMPUT 404

Web Applications and Architecture

Part 08: AJAX

Created by
Abram Hindle (abram.hindle@ualberta.ca)
and Hazel Campbell (hazel.campbell@ualberta.ca).
Copyright 2014-2023.

AJAX

AJAX

What is AJAX?

  • Asynchronous JavaScript and XML
    • Client Side
    • Allows Javascript to make HTTP requests and use the results without redirecting the browser.
    • Enables heavy-clients and lightweight webservices
    • Can be used to avoid presentation responsibility on the webservice.
    • JSON is a common replacement for XML
    • Modern social media is heavy on Ajax

AJAX Disadvantages

  • You have to manage History, Back button, Bookmarks in JS
  • Security: browsers heavily restrict AJAX to prevent abuse
    • Same-Origin Policy
  • Even more HTTP requests, CPU and RAM

Making Requests

Use JS to make an HTTP request and get the content

Fetch API

Stop trying to make fetch happen! Mean Girls. Paramount Home Entertainment, 2005. Performed by Rachel McAdams.
<blockquote>
  <img height="400" class="centered" id="fetchexample" 
    alt="Stop trying to make fetch happen!">
  <cite><em>Mean Girls.</em> Paramount Home Entertainment, 2005. Performed by Rachel McAdams.</cite>
  <button type="button" onclick="fetchExample()">Fetch!</button>

    
function fetchExample() {
    // https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#Example 2019-02-14
    var img = document.querySelector("img#fetchexample");
    var request = new Request("../images/fetch.gif");
    var promise1 = fetch(request);
    var promise2 = promise1.then(function(response) {
        console.log("Got headers!");
        return response.blob(); // return a promise for raw binary data
    });
    promise2.then(function(blob) {
        console.log("Got data blob!");
        var objectURL = URL.createObjectURL(blob);
        img.src = objectURL;
    });
}
// Note in the DOM the img's src= attribute after running!</pre></code>

Promises, Promises

function makeAPromise(data) {
    return new Promise((resolve, reject) => {
      resolve(data);
    });
  }
  function promiseExample() {
    // .then returns a NEW promise
    var promise1 = makeAPromise("X");
    var promise2 = promise1.then((data2) => {
      // inner function gets called after the promise is resolved
      alert("data2: " + data2);
      return "Y";
    });
    // .then calls its argument with the return value of the
    //     previous promises's callback function
    var promise3 = promise2.then((data3) => {
      alert("data3: " + data3);
    });
  }

Timers

  • window.setInterval lets you run a function every so many milliseconds.
  • window.setTimeout lets you run a function once after so many milliseconds.

Promises Execution Order

function promiseTimerExample() {
  console.log("1. First line of promiseTimerExample");
  const promise1 = new Promise((resolve, reject) => {
    console.log("2. In new Promise #1 executor function")
    window.setTimeout(() => {
      console.log("4. In window.setTimeout callback function")
      resolve(333);
    }, 1000);
  })
  const promise2 = promise1.then((value) => {
    console.log("5. In promise1.then "onfulfilled" function", value);
    return 777;
  });
  const promise3 = promise2.then((value) => {
    console.log("6. In promise2.then "onfulfilled" function", value);
  })
  console.log("3. LAST line of promiseTimerExample");
}

Function Passed to then Can Return Promise

function promise2TimerExample() {
  console.log("1. First line of promiseTimerExample");
  const promise1 = new Promise((resolve, reject) => {
    console.log("2. In new Promise #1 executor function")
    window.setTimeout(() => {
      console.log("4. In window.setTimeout callback function")
      resolve(333);
    }, 1000);
  })
  const promise2 = promise1.then((value) => {
    console.log("5. In promise1.then function", value);
    return new Promise((resolve, reject) => {
      window.setTimeout(() => {
        console.log("6. Second timer timed out");
        resolve(777);
      }, 2000);
    });
  });
  const promise3 = promise2.then((value) => {
    console.log("7. In promise2.then function", value);
  })
  console.log("3. LAST line of promiseTimerExample");
}

Promise dot-chaining

  var request = new Request("images/fetch.gif");
  var promise1 = fetch(request);
  var promise2 = promise1.then(function(response) {
    console.log("Got headers!");
    return response.blob(); // return a promise for raw binary data
  });
  promise2.then(function(blob) {
    console.log("Got data blob!");
    var objectURL = URL.createObjectURL(blob);
    img.src = objectURL;
  });
}

is the same as

var request = new Request("images/fetch.gif");
fetch(request).then(function(response) {
  console.log("Got headers!");
  return response.blob(); // return a promise for raw binary data
}).then(function(blob) {
  console.log("Got data blob!");
  var objectURL = URL.createObjectURL(blob);
  img.src = objectURL;
});

Fetching JSON

Let's make a generic JSON GET function

function fetchJSON(url) {
  var request = new Request(url);
  return fetch(request).then((response) => {
    if (response.status === 200) { // OK
      return response.json(); // return a Promise
    } else {
      alert("Something went wrong: " + response.status);
    }
  });
}

Do something with the JSON

var getterID; // global
function startGetting() {
  getterID = window.setInterval(() => { // callback
    var now = new Date();
    var s = 1 + (now.getSeconds() % 4); // remainder
    var url = s + ".json" // 1.json 2.json 3.json...
    fetchJSON(url).then((json) => { // another callback
      console.log(json); // browser turned the JSON into an object
      text = json.message; // it has properties
      document.querySelector("#ajaxy").innerText = text;
    });
  }, 1000); // 1 second or 1000ms
}
function stopGetting() {
  window.clearInterval(getterID);
}
<button type="button" onclick="startGetting()">Start Getting</button>
<button type="button" onclick="stopGetting()">Stop Getting</button>
<blockquote id="ajaxy"></blockquote>

JSON

  • JavaScript Object Notation
  • Strict subset of JavaScript
  • http://json.org/
  • JSON.parse parses JSON text into an Object
  • JSON.stringify turns an Object into JSON text
function stringifyExample() {
    var obj = { "food":"hotdog", 
                "condiments":["ketchup","mustard","cheese"],
                "sausage":"weiner"
    };
    document.querySelector("#hotdog").value = 
      JSON.stringify(obj, null, " "); // pretty print
  }
  function parseExample() {
    text = document.querySelector("#hotdog").value;
    var newObj = JSON.parse(text); 
    document.querySelector("#sausage").innerText = newObj.sausage;
  }

Design With AJAX

Design Suggestions:

What are your events?

  • Per user input?
  • Per user commit?
  • Time based?
  • Per Server action?
    • Polling?
  • Data?
  • Content oriented?
  • Messages?
  • Multimedia?

AJAX Observer Pattern

  • Observer pattern is where an observable keeps a collection of observers (listeners) and notifies those observers if anything changed by sending an update message.
  • This works great with AJAX if the observable is held client side in a browser and the observer is client side in the browser! Go ahead!

AJAX Observer Pattern

Observer UML Diagram

AJAX Observer Pattern

  • Still works well with observable in browser and the observers server-side, the client simply notifies via the server's observers whenever an update occurs (but it should also communicate some lightweight state).
  • Due to the lack of a client-side HTTP server it is problematic to do the observer pattern with client side observers.

Observing the Server

  • HTTP is stateless, so a client needs to communicate somehow all of the objects it is observing.
    • Perhaps a serverside Observer proxy that stores observables for a client
  • Clients need to poll the server to check if there are updates. For the observer pattern to work this polling should allow the server to send update commands.
  • Due to bandwidth concerns and latency concerns, an update from the server should probably include relevant data

Observing the Server

  • Fighting against:
    • Latency
    • Bandwidth
    • Lack of communication channels
    • Lack of ability to push messages to a client
    • Polling
    • Timer smashing

Observing the Server

Solutions?

  • Polling: the most common
  • Service Workers + Push API: not supported in all browsers
  • Comet "long polling": difficult server-side support
  • Websockets: need to make a websocket server

Polling the Server

  • Don't send too many requests
  • Batch (bundle together) requests to the server
  • Minimize the number of timers and the frequency of timers
    • E.g. if drawing, a user doesn't need more than 30FPS!
  • Don't make requests until the previous request finished...
  • Don't make requests you don't have to

AJAX Resources

More JavaScript: async and await

  • async functions
    • Return a promise!
  • await in async functions
    • blocks code execution (stop and wait for the promise to resolve)

Async Await Example

var getterID2; // global
async function get2() {
  var now = new Date();
  var s = 1 + (now.getSeconds() % 4); // remainder
  var url = "../" + s + ".json" // 1.json 2.json 3.json...
  var json = await fetchJSON(url);
  console.log(json); // browser turned the JSON into an object
  var text = json.message; // it has properties
  document.querySelector("#ajaxy2").innerText = text;
}
function startGetting2() {
  getterID2 = window.setInterval(get2, 1000); // 1 second or 1000ms
}
function stopGetting2() {
  window.clearInterval(getterID2);
}
<button type="button" onclick="startGetting2()">Start Getting</button>
<button type="button" onclick="stopGetting2()">Stop Getting</button>
<blockquote id="ajaxy2"></blockquote>

async/await Disadvantages

  • Execution stops at await, instead of continuing in parallel
    • In threads if the browser supports it
  • With .then(...) in a loop, the loop completes instantly and each callback function can run in parallel as soon as its ready
  • With await in a loop, the loop will keep stopping each time it gets to the await

Observer Example

License

Copyright 2014-2023 ⓒ Abram Hindle

Copyright 2019-2023 ⓒ Hazel Victoria Campbell and contributors

Creative Commons Licence
The textual components and original images of this slide deck are placed under the Creative Commons is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Other images used under fair use and copyright their copyright holders.

License


Copyright (C) 2019-2023 Hazel Victoria Campbell
Copyright (C) 2014-2023 Abram Hindle and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN.

01234567890123456789012345678901234567890123456789012345678901234567890123456789