background

Traditional file download on the WEB is generally only applicable to single file download. If multiple files are downloaded at the same time, it is generally required to use the client to achieve, such as Baidu WEB disk and Xunlei. This paper discusses the feasibility of several WEB – end multi – file download methods.

plan

1. Delay launching multiple download requests

function sleep(time = 1000) {
  return new Promise(resolve= > {
    setTimeout(resolve, time);
  });
}
const files = ["1m.zip"."2m.zip"."3m.zip"];
const download1 = () = > {
  files.forEach(async (file, i) => {
    // Delay the download
    await sleep(i * 1000);
    const a = document.createElement("a");
    a.href = `path/${file}`;
    a.download = file;
    a.click();
  });
};
Copy the code

I want to emphasize the point of delay here. You can try to make multiple download requests at the same time and end up downloading only the last file correctly, with the rest showing cancelled errors in DevTools’ Network.

The reason is that in this download mode, a label is used to trigger the download for multiple times. A label means a page jump to the browser. When a label is triggered for multiple times without delay, the browser considers that the user has a new access target, so it cancels the request to visit the previous page.

Disadvantages:

  1. The browser will prompt the user whether to allow multiple files to be downloaded automatically, and the user needs to confirm manually
  2. When the number of downloaded files is too large and the files are too small, the delay takes up too much time.

A similar download method, using window.open(href, “_blank”) instead of the A tag, also has obvious drawbacks. First, by default, browsers prevent multiple tabs from popping up, and only the first file can be successfully downloaded (as opposed to the A TAB). Secondly, users are authorized to open multiple browser tabs manually. When too many files are downloaded and a row of pages are opened at the same time, the physical examination will be very poor.

2. The server generates archive files

When you have server-side permissions, you can have the server preprocess all files to be downloaded as a ZIP package. You can download a single file on the WEB. Because it is the server side operation, not in the front end category, do not discuss.

3. The client generates archive files

If the server cannot be controlled, use this method.

One of the best-known libraries for archiving is JSZip. But this time, another library is used: client-zip. The advantage over JSZip is that some of the logic is written by WebAssembly and read and write by streaming. README introduction is 40 times faster than JSZip and the code size is smaller, but compatibility issues need to be taken care of.

const download3 = async() = > {const responses = await Promise.all(files.map((file) = > {
    // Blob files can be stored in indexDB after downloading
    Check whether the file already exists in indexDB to avoid repeated downloads
    return fetch(`path/${file}`, { mode: "cors" });
  }));
  // Error handling can be customized
  if (responses.some(response= > {
    return response.status === 401;
  })) {
    alert("Unauthorized download request");
    return;
  }
  // Archive all downloaded files
  const blob = await downloadZip(responses).blob();
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.download = "files.zip";
  a.href = url;
  a.click();
  URL.revokeObjectURL(url);
  // If indexDB is stored, clear the corresponding cache
};
Copy the code

Advantages:

  1. Download without delay
  2. Download in fetch mode, download progress can be calculated and displayed in UI
  3. The download process can be authenticated
  4. You can save downloaded files in indexDB to prevent downloaded files from being lost

Disadvantages:

  1. The browser limits bloB size (operating system, disk, and memory size), and indexDB can be used to overcome this limitation (in this case, the browser limits bloB size to 500MB, which modern browsers far exceed).
  2. If indexDB is used, additional cache space will be used

4. file-system-access

Using the new native file API to process files will greatly simplify the multi-file download process.

const download4 = async() = > {// You can store handlers to indexDB to avoid requesting permissions every time
  const dirHandle = await window.showDirectoryPicker();
  const promises = files.map(async file => {
    // You can start by traversing the files in dirHandler and skip the downloaded files. You can also store downloaded files in indexDB for subsequent filtering.
    const res = await fetch(`path/${file}`, { mode: "cors" });
    // Create a new file handler
    const newFileHandle = await dirHandle.getFileHandle(file, { create: true });
    const writable = await newFileHandle.createWritable();
    // Pipeline streaming storage
    awaitres.body! .pipeTo(writable);// Here you can update the download progress and display it in the UI.
  });
  await Promise.all(promises);
  alert("Download completed");
};
Copy the code

Even when the browser is closed, the download progress of this solution is not lost, and file-system access has many excellent capabilities for blind people

  1. Append to write at the specified location
  2. Navigate through the download directory to display the downloaded files
  3. File stream writing, high speed and low memory footprint
  4. The downloaded file status can be stored in indexDB without loss of progress
  5. You can set naming suggestions for downloaded files
  6. Store files to type default directory
  7. .

A more complete example based on the code above:

// Obtain read and write permissions
async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = "readwrite";
  }
  if ((await fileHandle.queryPermission(options)) === "granted") {
    return true;
  }
  if ((await fileHandle.requestPermission(options)) === "granted") {
    return true;
  }
  return false;
}
Copy the code
import { get, set } from "idb-keyval";
const download4 = async() = > {// Try to get the directory Handle from indexDB
  let dirHandle = await get("dirHandle");
  if(! dirHandle) { dirHandle =await window.showDirectoryPicker();
    // Save the download path Handle to avoid requiring the user to select it every time
    await set("dirHandle", dirHandle);
  }
  const hasPermission = await verifyPermission(dirHandle, true);
  if(! hasPermission) { alert("Unauthorized read/write");
    return;
  }
  // Filter downloaded files
  for await (let entry of dirHandle.values()) {
    const filename = entry.name;
    let index = files.indexOf(filename);
    if(index ! = = -1) {
      console.log(` file${filename}Have downloaded `)
      files.splice(index, 1); }}const promises = files.map(async file => {
    const res = await fetch(`path/${file}`, { mode: "cors" });
    const newFileHandle = await dirHandle.getFileHandle(file, { create: true });
    const writable = await newFileHandle.createWritable();
    await res.body.pipeTo(writable);
    // Here you can update the download progress and display it in the UI.
  });
  await Promise.all(promises);
  alert("Download completed");
};
Copy the code

Advantages:

  1. Advantages of other schemes
  2. The best performance
  3. Basic implementation is simple
  4. No additional archive files are generated

Disadvantages:

  1. Compatibility rush

conclusion

For compatibility, you can choose to generate archive files on the client side. If you only consider Chrome, try file-system-Access, which is the future of video clips, image editing and file processing.

If you have other plans, please discuss more.