Skip to content
This repository was archived by the owner on Apr 14, 2025. It is now read-only.

Lesson 1.7

Mary Alice Moore edited this page Oct 4, 2024 · 3 revisions

Overview

JavaScript is synchronous in nature. This means JavaScript runs code in the sequence they’re written.

Asynchronous is the opposite of synchronous.

Asynchronous code will not be executed immediately. They will only be executed after a certain event occurs.

One example is when we click a button.

const button = document.querySelector('button')

button.addEventListener('click', function (event) {
  console.log('Button clicked')
})

The asynchronous part happens within addEventListener. The callback doesn’t run unless a click event occurs. This means JavaScript has effectively waited for something to happen. This wait is asynchronous — it delayed the code from being executed immediately.

Callbacks are a fundamental concept in asynchronous JavaScript.

Another example is executing a function after a certain amount of time has passed. We can do this with setTimeout.

setTimeout(function () {
  console.log('1 second has passed')
}, 1000)

What is an API?

API stands for Application Programming Interface. It is code that allows you to “talk” to other programs through the code you write. Other programs in this case refers to code that others have written.

For example, the code above using document.querySelector that lets you select an element is one small part of the DOM API.

Ajax vs Asynchronous JavaScript

AJAX (or Ajax) means Asynchronous JavaScript and XML. When people say AJAX, they normally refer to the process of fetching a resource.

  1. Send a request to a server
  2. Wait for a response to come back

The term AJAX was used in the past because the responses will come back in an XML form. The term AJAX is outdated since we don’t use XML anymore, we use JSON now.

JSON

JSON stands for JavaScript Object Notation.

We use JSON as the main format to pass information between browsers and servers today. A JSON Object looks like a JavaScript object. The major difference between JSON and JavaScript is:

  1. JSON can only contain strings
  2. JSON property and value pairs are always wrapped with double quotes
{
  "firstName": “Sam”,
  "lastName": “Filip”,
  "occupation": "engineer",
  "age": "35"
}

Fetching Resources

There are two ways to fetch resources.

  1. Using XMLHTTPRequest (XHR for short)
  2. Using the Fetch API

Both XHR and Fetch are well supported on all browsers. The major difference is that XHR uses callbacks while Fetch uses Promises. Fetch is the standard practice today in fetching resources on the web.

The Fetch API

The Fetch API is a newer method that lets you perform Ajax.

To send a request with Fetch:

fetch(url, options)

  • url is the address you want to request information from.
  • options is a JavaScript object that lets you provide extra options.

To see the response from Fetch, you need to write a then statement.

fetch('https://link-to-resource')
  .then(response => console.log(response))

Before we can use the data in the response variable, we need to convert this readable stream into JavaScript before we can use it. This is done with response.json that comes with a Fetch response.

Then you get the payload in the next then method.

fetch('https://link-to-resource')
  .then(response => response.json())
  .then(data => console.log(data))

Understanding Promises

A JavaScript Promise is an if-else statement for the future. We don’t know whether the statement will flow into if or else until the future arrives. So promises have three states:

  • Pending
  • Resolved (or fulfilled)
  • Rejected

All promises have the then and catch methods.

  • We act on resolved promises in then.
  • We can act on rejected promises in catch.

If you return a value from a then call, you can use that value in the next then call. response.json in the Fetch API uses this feature. Remember, a Fetch request is a promise.

fetch('https://link-to-resource')
  .then(response => response.json())
  .then(data => {/* do something with data */}

If an error occurs in a promise chain, the error will be passed directly into the next catch call. It will skip any then calls along the way.

This means we can handle all errors with a single catch call:

promise
  .then(face => new Error('new error’)) // Throws an error
  .then(face => console.log(‘something…’)) // Skipped
  .catch(face => console.log(‘error’)) // Jumped here 

All promises have a finally method. This method will be called after all then and catch calls. It is optional and not always needed and mainly used for cleanup.

promise.then(/*…*/).catch(/*…*/).finally(/*…*/)

Async/Await

The purpose of async / await is to make complicated asynchronous code easier to understand. It does this by making asynchronous code look synchronous.

You declare asynchronous functions with an async keyword. The async keyword must be placed before the function keyword.

async function functionName(arguments) {
  // Do something asynchronous
}

You can also create asynchronous functions with the arrow function syntax. In this case, you write async before the arguments.

const functionName = async arguments => {
  // Do something asynchronous
}

You call asynchronous functions the same way you call normal functions.

// Calling an async function
anAsyncFunction()

Asynchronous functions use promises underneath the hood so they always return a promise. The await keyword lets you wait for a promise to resolve (or reject) before proceeding to the next line in the code.

The await keyword must be written inside an asynchronous function.

async function getSomething() {
  await fetch('https://resource-link')
}

You can use a try / catch block to check whether the promise resolves or rejects. This is very similar to then and catch calls within a promise.

async function getSomething() {
  try {
    await fetch('https://resource-link')
    // Promise resolves.
  } catch (error) {
    // Promise rejects.
  }
}

You don’t have to nest all await calls inside a try / catch block. When we call the async function, we can attach a catch block to handle all errors.

async function getSomething() {
  const response = await fetch('https://resource-link')
  const data = await response.json()
  // Do something with data
}

getSomething().catch(error => {
  /* Handle the error */
})

Now put this into action...

Remove Custom Hook

  • Open src/App.jsx
  • Delete the useSemiPersistentState function call from App
  • Copy the useState and useEffect hooks from useSemiPersistentState function back into App
  • Delete the useSemiPersistentState function
  • Run your application and view in browser
    • Verify that your Todo List still appears correctly

Async

Currently our list data is retrieved synchronously from the browser's storage, but in the next lesson we will be fetching it asynchronously from an API. Let's update our code to mimic asynchronous data fetching:

  • Below the todoList state, define a useEffect React hook with an empty dependency list
  • Inside the side-effect handler function, define a new Promise and pass in a callback function with parameters resolve and reject
    • hint: Promise() constructor
  • To mimic a loading delay, inside the callback function declare a timeout (hint: setTimeout method) with the following arguments:
    • callback: function with no parameters
    • delay time: 2000 milliseconds (2 seconds)
  • Inside the timeout callback function, call the parameter resolve which is a callback function for when the Promise is successful and pass it an Object with property data as a nested empty Object
  • Inside the data object, add a property todoList and set it's value to the initial/default list state (copy from useState hook)
  • Update the default state for todoList to be an empty Array
  • View your application in the browser
    • Notice that the Todo List is now empty and doesn't persist after refresh

So what's missing? We are retrieving our list from localStorage but we aren't updating our todoList state with the data so it remains empty. Let's fix that:

  • Chain a then method to your Promise constructor and pass it a function with parameter result
  • Inside the function, use your state setter to update the list and pass the todoList from your result object
  • View your application in the browser

You'll notice now that the list is being saved in localStorage but after refreshing the page it is reset to empty. This is because our other side-effect is overwriting the data before the asynchronous fetch is complete.

We need a way to know that the data is still loading before trying to update it in localStorage.

Add Loading State

  • After the todoList state declaration, create a new state variable named isLoading with update function named setIsLoading with default value true
  • Inside the second useEffect hook (with todoList dependency), add an if statement to check that isLoading is false before setting localStorage

Now we just need to way to turn loading off once the data has been fetched.

  • Revisit the then callback in the first useEffect hook
  • After setting the todoList state, add another line to set isLoading state to false
  • View your application in the browser
    • Enter a new todo in "Add Todo" form and submit
    • Reload the page and wait 2 seconds
    • Notice that the saved item now appears in the list

Great! Now our data is being saved properly again, but that delay in loading makes it seem like the list is empty at first. Let's add a loading indicator to prevent confusion.

Create Conditional Loading Indicator

  • Inside the App JSX, create a new paragraph element above TodoList with text "Loading..."
  • View your application in the browser
    • Reload the page and notice that the loading message is visible
    • Wait 2 seconds and notice that the Todo List appears but the loading message is still there

We don't want to always show the loading indicator, it should conditionally appear based on our isLoading state.

  • Using a ternary operator inside JSX, if isLoading is true render the loading message, otherwise render the TodoList component
  • View your application in the browser
    • Reload the page and notice that the loading message is visible
    • Wait 2 seconds and notice that the loading indicator disappears when the Todo List becomes visible

Clone this wiki locally