Show a Progress Indicator for a Fetch Request with the Streams API

AnthumChris collected some JavaScript scripts to track the progress of fetch requests that download files/data. It works by leveraging the ReadableStream interface from the Streams API.

A “simple” example is this:

fetch('https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg')
.then(response => {
  if (!response.ok) {
    throw Error(response.status+' '+response.statusText)
  }

  if (!response.body) {
    throw Error('ReadableStream not yet supported in this browser.')
  }

  // to access headers, server must send CORS header "Access-Control-Expose-Headers: content-encoding, content-length x-file-size"
  // server must send custom x-file-size header if gzip or other content-encoding is used
  const contentEncoding = response.headers.get('content-encoding');
  const contentLength = response.headers.get(contentEncoding ? 'x-file-size' : 'content-length');
  if (contentLength === null) {
    throw Error('Response size header unavailable');
  }

  const total = parseInt(contentLength, 10);
  let loaded = 0;

  return new Response(
    new ReadableStream({
      start(controller) {
        const reader = response.body.getReader();

        read();
        function read() {
          reader.read().then(({done, value}) => {
            if (done) {
              controller.close();
              return; 
            }
            loaded += value.byteLength;
            console.log(Math.round(loaded/total*100)+'%');
            controller.enqueue(value);
            read();
          }).catch(error => {
            console.error(error);
            controller.error(error)                  
          })
        }
      }
    })
  );
})
.then(response => response.blob())
.then(data => {
  console.log('download completed');
  // document.getElementById('img').src = URL.createObjectURL(data);
})
.catch(error => {
  console.error(error);
});

If that code confuses you, it’s mostly the Fetch + ReadableStream example code from MDN. The key additions are:

  • Extract the length from the content-length header. If some encoding is used, the server must send the x-file-size header instead
  • Store total bytes and loaded
  • Increment loaded and calculate the progress (here: logged to the console)
  • Do something with the data (here: commented out)

Can also be used from within a ServiceWorker.

Fetch & Streams API Progress Indicator Source Code (GitHub) →
Fetch & Streams API Progress Indicator Demos →

ohmyfetch — A better fetch API

ohmyfetch is a better fetch API. Works on node, browser and workers.

import { $fetch } from 'ohmyfetch'

Import it and use it as you’d use regular fetch. On top of that, you can use some of the extra convenience methods and options — such as easy response parsing, JSON body, Auto Retry, … — this package provides.

ohmyfetch — A better fetch API →

JavaScript: Hostnames as self-executing fetches

In a conversation between Mathias Bynens and Ingvar Stepanyan, an idea popped up: what if a hostname — such as www.bram.us — would be valid JavaScript? Using a JavaScript Proxy, that’s perfect possible.

Building further upon that is proxy-www, which creates self-executing fetches from them.

const www = new Proxy(new URL('https://www'), {
    get: function get(target, prop) {
        let o = Reflect.get(target, prop);
        if (typeof o === 'function') {
            return o.bind(target)
        }
        if (typeof prop !== 'string') {
            return o;
        }
        if (prop === 'then') {
            return Promise.prototype.then.bind(fetch(target));
        }
        target = new URL(target);
        target.hostname += `.${prop}`;
        return new Proxy(target, { get });
    }
});

Usage:

www.baidu.com.then(response => {
    console.log(response.status);
})

My inner geek rejoices 🤓

proxy-www

SWR — React Hooks library for data fetching

From the Next.js folks comes SWR, a React Hooks library for data fetching built on Suspense:

The name “SWR” is derived from stale-while-revalidate, a cache invalidation strategy popularized by HTTP RFC 5861. SWR first returns the data from cache (stale), then sends the fetch request (revalidate), and finally comes with the up-to-date data again.

It can avoid scenarios where you need to pass down a fetched user into several child components

import useSWR from 'swr'

const fetcher = (...args) => fetch(...args).then(res => res.json())

function useUser (id) {
  const { data, error } = useSWR(`/api/user/${id}`, fetcher)

  return {
    user: data,
    isLoading: !error && !data,
    isError: error
  }
}
// page component
function Page () {
  return <div>
    <Navbar />
    <Content />
  </div>
}

// child components
function Navbar () {
  return <div>
    ...
    <Avatar />
  </div>
}

function Content () {
  const { user, isLoading } = useUser()
  if (isLoading) return <Spinner />
  return <h1>Welcome back, {user.name}</h1>
}

function Avatar () {
  const { user, isLoading } = useUser()
  if (isLoading) return <Spinner />
  return <img src={user.avatar} alt={user.name} />
}

With SWR being used, this will result in only 1 request to /api/user/${id}.

The most beautiful thing is that there will be only 1 request sent to the API, because they use the same SWR key and the request is deduped, cached and shared automatically.

SWR — React Hooks library for data fetching →

redaxios – The Axios API, as an 800 byte Fetch wrapper.

Axios has a great API that developers love. Redaxios provides that API in 800 bytes, using native fetch().

For those searching for ways to shave a few kilobytes off of their bundles, that’s less than 1/5th of the size. This is made possible by using the browser’s native Fetch API, which is supported in all modern browsers and polyfilled by most tools including Next.js, Create React App and Preact CLI.

To swith from axios to redaxios, you only need to adjust your import statement

import axios from 'redaxios';
// That's it! Use as you would normally

redaxios