Eazip.js
EazipGitHub
Guides

Show ZIP Progress and Let Users Cancel or Retry

Build a real download experience around a ZIP job — live progress from startZip(), a cancel button via abort(), and a retry that handles failed and partial outcomes correctly.

A ZIP job that takes more than a second needs three affordances: progress the user can see, a cancel that actually stops work, and a retry that behaves sensibly when only part of the job failed. This guide builds all three — in React (where the tray gives you most of it) and in plain JavaScript with startZip().

The complete example

In plain JavaScript, startZip() returns an observable job instead of a promise-only call:

export-with-progress.ts
import { startZip } from '@eazip/core';

const progressBar = document.querySelector<HTMLProgressElement>('#progress')!;
const cancelButton = document.querySelector<HTMLButtonElement>('#cancel')!;

export function runExport(files: { url: string; filename: string }[]) {
  const job = startZip({ files, zipName: 'export.zip' });

  const unsubscribe = job.subscribe(() => {
    const snapshot = job.getSnapshot();

    if (snapshot.progress) {
      progressBar.max = snapshot.progress.totalFiles ?? 0;
      progressBar.value = snapshot.progress.completedFiles ?? 0;
    }
  });

  cancelButton.onclick = () => job.abort();

  job.done
    .then((result) => result.download())
    .catch((error) => showRetryUi(error, () => runExport(files)))
    .finally(() => unsubscribe());
}

The pieces: subscribe() fires on every snapshot change, getSnapshot() returns the current immutable state (status, progress, zips, errors), abort() stops Local work, and done resolves with usable output or rejects with the terminal error.

Getting each affordance right

Progress: the snapshot's progress reports Local file and byte counts (it is null for Cloud jobs — show an indeterminate state there, since the work runs server-side). Prefer "12 of 40 files" over a bare percentage; it stays honest when file sizes vary wildly.

Cancel: job.abort() (or an AbortSignal passed as signal in the options, if you already manage one) stops the work — status becomes aborted, and done rejects. Cancelling is a normal outcome, not an error to toast about.

Retry — the subtle one. Two different situations wear the retry label:

  • failed — no usable output. Retry by starting a new job with the same inputs, as in the example. If failures came from expired signed URLs, re-fetch fresh URLs from your endpoint first.
  • partial — usable output with skipped files. Don't silently restart everything: offer the download plus a "N files skipped" notice, and if you retry, retry only the entries listed in errors. See Partial results and errors.

FAQ

Why does my progress bar jump straight to done for small exports?

Small Local jobs finish in milliseconds. Consider showing progress UI only after ~300 ms of processing — instant completion needs no ceremony.

Does cancel stop in-flight network requests?

abort() stops the Local pipeline, including its fetching; partially fetched sources are discarded. For Cloud jobs it stops polling in the page — see the job model in ZIP jobs.

Can I show progress for a Cloud job?

Snapshot progress is null for Cloud — show an indeterminate spinner with the job status instead. The tray already does exactly this.

Where do per-part downloads fit?

Split output (maxZipSizeBytes) lands in the snapshot's zips array — render one action per entry, or call downloadAll(). See Multi-ZIP splitting.