JavaScript: Asynchronous JavaScript and fetch

Asynchronous JavaScript

Asynchronous JavaScript

some tasks in JavaScript may be scheduled to run in parallel (in particular tasks related to input/output):

  • network requests
  • reading / writing files in node.js
  • timers
  • ...

Tools for asynchronous JavaScript

  • callbacks, promises, async / await: for parallelized input/output
  • (web) workers: for parallelized CPU-intense tasks (JavaScript code actually runs in parallel)

Asynchronous JavaScript

  • callbacks: traditional way to handle asynchronous requests
  • promises and .then()
  • promises and async / await

Asynchronous JavaScript

possibilities for making network requests:

  • fetch (promises)
  • axios (promises)
  • jQuery
  • XMLHttpRequest

Asynchronous JavaScript

examples: requests with axios (await), axios (then) and jQuery (callback):

const res = await axios(url);
console.log(res.data);
axios(url).then((res) => console.log(res.data));
jquery.getJSON(url, (data) => console.log(data));

Fetch

Fetch

example that can be executed in the browser console:

// request a URL and wait for the response header
res = await fetch(
  'https://jsonplaceholder.typicode.com/todos'
);

// wait for the response body and read it as JSON data
content = await res.json();

console.log(content);

Fetch

example that can be executed in the browser console with any open website:

// request the current URL
res = await fetch("/");

// wait for the response body and read its text content
content = await res.text();

console.log(content);

Asynchronous functions

Asynchronous functions

Functions that perform input / output functionality are often declared as asynchronous functions.

Asynchronous functions can run in parallel to other code: E.g. for loading multiple files over the network in parallel.

Asynchronous functions

defining an asynchronous function:

async function loadTodo(id) {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/todos/${id}`
  );
  const data = await res.json();
  return data;
}

Asynchronous functions

run the asynchronous function in parallel multiple times (you can watch the requests in your browser console):

for (let i = 0; i < 10; i++) {
  loadTodo(i);
}

Fetch options

Fetch options

example of a fetch call with some options:

fetch(url, {
  method: 'POST',
  cache: 'no-cache',
  body: '{"text": "learn fetch"}',
  headers: { 'content-type': 'application/json' },
});

Fetch and error handling

Error handling

various errors may occur when fetching data:

  • browser is offline (no reply)
  • server responds with a 404 or similar
  • receives non-text or empty reply

Error handling: checking status

by default a reply with an error code (e.g. 404 or 500) is considered a success

async function fetchTodos () => {
  const res = await fetch(
    'https://jsonplaceholder.typicode.com/todos'
  );
  if (!res.ok) {
    throw new Error(res.statusText);
  }
  const todos = await res.json();
  return todos;
};

Promises

Promises

Promise: JavaScript class that represents future results

Promises are used internally when async / await is used

Promises

we can access promises if we "forget" to await an asynchronous result:

// a will be a promise
const a = fetch('...');

// b will be the actual result
const b = await a;

Promises

real-world example:

You place an order at a fast food restaurant. The cashier hands you this receipt:

order #42:

- cheeseburger
- small fries

The order will be served to you at your seat when ready.

the receipt is a "promise" - a representation of a future result

Promises

waiting for a promise to resolve:

  • await
  • .then()

Promises advanced

Creating custom promises

A promise that, after 1 second, either results in the string 'hello' or fails

const getReply = new Promise((resolve, reject) => {
  setTimeout(() => {
    if (Math.random() > 0.5) {
      resolve('hello');
    } else {
      reject('no access');
    }
  }, 1000);
});

Promise.all

Creating a promise that resolves when multiple asynchronous tasks have been completed:

const fetchJson = async (url) => {
  const res = await fetch(url);
  return await res.json();
};

const [users, todos] = await Promise.all([
  fetchJson('https://jsonplaceholder.typicode.com/users'),
  fetchJson('https://jsonplaceholder.typicode.com/todos'),
]);

Promise.race

Use the first successful promise as the result:

const anyTodo = await Promise.race([
  fetchJson('https://jsonplaceholder.typicode.com/todos/1'),
  fetchJson('https://jsonplaceholder.typicode.com/todos/2'),
  fetchJson('https://jsonplaceholder.typicode.com/todos/3'),
]);

Exercises

Exercises

Axios

Axios

widely used library that provides more functionality / a simpler interface than fetch

Axios

Fetching JSON data:

const todos = await axios(
  'https://jsonplaceholder.typicode.com/todos'
);

Status codes in Axios

default behavior:

  • status codes in the 200 range: successful promise resolution
  • status codes in 400 and 500 ranges: promise rejection

Axios advanced

Global defaults

examples:

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] =
  'application/x-www-form-urlencoded';

Custom instances & defaults

const todosAxios = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com/todos',
  timeout: 2000,
});

todosAxios.get('/').then(console.log);
todosAxios.get('/1').then(console.log);

Interceptors

Interceptors may be added to the configuration; they are called automatically on either requests or responses and can contain additional logic to modify them

const requestLogger = requestConfig => {
  console.log('sending request', requestConfig);
  return requestConfig;
};
todosAxios.interceptors.request.use(requestLogger);
const responseLogger = response => {
  console.log('received response', response);
  return response;
};
todosAxios.interceptors.request.use(responseLogger);