Coder Social home page Coder Social logo

anthumchris / fetch-progress-indicators Goto Github PK

View Code? Open in Web Editor NEW
338.0 10.0 36.0 303 KB

Progress indicators/bars using Streams, Service Workers, and Fetch APIs

Home Page: https://fetch-progress.anthum.com/

License: MIT License

CSS 6.15% JavaScript 35.13% HTML 58.72%
fetch-api service-worker javascript-fetch javascript streams-api stream

fetch-progress-indicators's Issues

Add Example for inline `<img>` loading

Implement a Service Worker to intercept fetch events to show progress bars for inline <img> loading when src attribute is set. All existing fetch() calls should be removed and download progress monitoring should occur in the Service Worker

Make Mobile-Friendly

Development was lazily done on desktop, and these examples should be mobile-friendly/responsive and testable from phones

Add Example for File Upload

Progress indicators for file uploads (specifically indicators for Request objects) would be a great example. At the time of writing, this doesn't seem possible yet, and the Request docs do indicate ReadableStream may be used as a request body

String body: sent

// Post "hello" body.  Browser sends and server responds with what it receives.
fetch(
  new Request('https://dev.anthum.com/post-test/', {
    body: 'hello',
    headers: {
      'content-type': 'text/plain'
    },
    method: 'POST'
  })
)
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error(error));

ReadableStream body: not sent

This may just be bad experimental code on my part or the browsers do not yet support it.

const stream = new ReadableStream({
  start(controller) {
    controller.enqueue(new TextEncoder().encode('hello'));
    controller.close();
  }
});

// Post ReadableStream body. Browser doesn't seem to send, and server responds with nothing received
fetch(
  new Request('https://dev.anthum.com/post-test/', {
    body: stream,
    headers: {
      'content-type': 'text/plain'
    },
    method: 'POST'
  })
)
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error(error));

Use TransformStream in Examples

Refactor with TransformStream as described by @ricea:

The examples would be cleaner with TransformStream, but unfortunately no browser is shipping it yet. You could be really concise, something like (totally untested)

fetchEvent.respondWith(new Response(response.body.pipeThrough(new TransformStream({
  transform(chunk, controller) {
    loaded += chunk.length;
    progress({loaded, total});
    controller.enqueue(chunk);
  }
})), response));

Add Aggregate Download Progress for Multiple Assets

A SW could be used to show aggregate/total progress indicators for multiple assets that download individually as part dependencies for a PWA. Video games loading screens are good examples of this. For large downloads that would exceed a few seconds, it would make sense to load the SW first, refresh, and then show progress for multiple assets in transit.

This example should leverage HTTP2 to ensure that totals for all assets are gathered from the onset. Assumption is that multiple responses will come in before downloads complete, and that assumption should be tested with various "total asset" values (i.e. 5, 10, 20, 30). Various browsers and back-end Nginx server may effect the total files that could start to download, hence affecting the "total bytes" aggregate.

This recent originated from a recent StackOverflow question "Show progress while caching files using a service worker"

Mobile Safari Does Not Show Errors

Demos do not work on Mobile Safari (versions 8 tested) and no errors show. Ensure that proper errors show if certain JS APIs are not yet implemented

Headers from original request not forwarded

Hi there -- thanks for these examples. One improvement I suggest is to show how to forward the headers from the original request:

return new Response(stream, { headers: response.headers });

Simpler solution for fetch-basic

In your fetch-basic example you read the response then "pipe" it to a new response:

...
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;
            progress({loaded, total})
            controller.enqueue(value);
            read();
          }).catch(error => {
            console.error(error);
            controller.error(error)                  
          })
        }
      }
    })
  );
})
.then(response => response.blob())
.then(data => {
  status('download completed')
  document.getElementById('img').src = URL.createObjectURL(data);
})
...

There is no need to perform this extra work:

const content = []; // <===
let bytesReceived = 0;

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  content.push(value); // <===
  bytesReceived += value.byteLength;
}

document.getElementById('img').src = URL.createObjectURL(new Blob(content));

Full example:

const progressIndicator = document.getElementById('progress');

const { headers, body } = await fetch(
  'https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg'
);

const contentLength = headers.get('Content-Length');
if (contentLength === null) throw new Error('No Content-Length response header');
const totalBytes = parseInt(contentLength, 10);
progressIndicator.max = totalBytes;

if (body === null) throw new Error('No response body');
const reader = body.getReader();

const content = []; // <===
let bytesReceived = 0;

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  content.push(value); // <===
  bytesReceived += value.byteLength;

  progressIndicator.value = bytesReceived;
}

document.getElementById('img').src = URL.createObjectURL(new Blob(content));
<progress id="progress" value="0"></progress>
<img id="img" alt="download-progress-img" src="data:,">

Use Proper Restart feature

A "Reload" button currently exists to start the test over by reloading the entire page. This was a fast solution, and ideally a "Restart" or "Run Again" button should be implemented to re-start the test without full page reload.

ReadableStream.cancel() can be used, which stops the network transfer and also currently shows partially loaded images, which is kind of cool for testing/observation load differences between Progressive and Baseline JPEG images

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.