Browser is cancelling multiple file download requests
Asked Answered
L

4

6

I am trying to download multiple files that the user has selected for download. However, the browser is cancelling all but last download request. If I am increasing the delay between the requests to say about 1 second, then those files get downloaded, but even in this case, sometimes some files are missed. The files are being downloaded from amazon s3 urls(i.e. these are CORS).

I am doing this by creating an anchor element with the url and then calling the click event on it using javascript.

downloadFile(url) {
    let a = document.createElement('a');
    a.id = url;
    // a.setAttribute('target', 'blank');
    a.download = '';
    a.href = url;
    // firefox doesn't support `a.click()`...
    // console.log('dowmloading ' + url);
    a.dispatchEvent(new MouseEvent('click'));
    a.remove();
}

Then on download button click event I'm doing this:

let delay = 0;
        urlList.forEach(url => {
            return setTimeout(downloadFile.bind(null, url), 100 * ++delay);
        });

Is there a way to accomplish this?

Why is the browser cancelling the requests?

Lamont answered 28/8, 2018 at 6:21 Comment(2)
Anyway, include code for a SSCCE and more details like what 'the browser' is, and if different 'the browser' implementations behave differently.Indre
This is the behaviour in both chrome and firefox browser. The request get cancelled immediately. I have latest version both of them.Lamont
O
8

Why is the browser cancelling the requests?

Because you are essentially "canceling navigation" by clicking the next link, before the browser is fully done with what it has to do when you clicked the previous one.

If this weren’t downloads, but regular links to pages, then this is the behavior you want - if the user clicks a link to page A first, but then looses patience and clicks on another link to page B, then the request for page A gets cancelled at that point - no need in loading two resources, when only one can be displayed at the same time anyway.

I don’t think there is much you can do about this, if you do not want to figure out a "magic number" of a timeout that somehow makes it "work" - especially since you won’t know if that works in general, or maybe just on your machine, with your internet connection, etc.

I am trying to download multiple files that the user has selected for download.

You could have those selected files wrapped into a container format - like for example ZIP (more than just a "container", strictly speaking) - dynamically on the server, so that the user just has to download one file only (which they will then have to unpack again on their own machine.)

Or you change your file selection process to begin with. Instead of having the user mark the files they want using checkboxes or something like that, present them direct links to the files instead maybe? Then they can click each one they want one after another, and the "normal" download functionality will take place.

Orate answered 28/8, 2018 at 7:2 Comment(1)
thanks for the explanation and also for suggesting another way to do it. I'll certainly try those.Lamont
A
2

@misorude is right about why they're getting canceled but a workaround is to use an iframe instead of an anchor tag to download a file.

a typescript implementation of a download file below:

export function downloadFile(downloadLink: string): void {
  const iframe = document.createElement("iframe");
  iframe.setAttribute("sandbox", "allow-downloads allow-scripts");
  iframe.src = downloadLink;
  iframe.setAttribute("style", "display: none");

  document.body.appendChild(iframe);
}

So it's similar to your implementation - only thing is, you'll have stray (non-displayed) iframes on you DOM. There's no way to tell when the iframe is done downloading the file so there isn't an adequate way to know when to remove them. But at least you wouldn't get your network requests canceled.

Apps answered 8/6, 2022 at 5:48 Comment(4)
What is the purpose of iframe.classList.add(iframeClassname); in this case?Pentaprism
This approach worked very well in my scenario. I think it is a better answer than the one chosen because it sticks with the solution of trying to download multiple files at once. To be more specific, this answer answers the "Is there a way to accomplish this?" question.Pentaprism
missed that. removed ` iframe.classList.add(iframeClassname);` thanks @GeraldoB.LandreApps
if i download many items, there will create many iframes, may be memory leak ? is there way we can know the file has been download completely and then destory the iframe?Merits
R
0

You can also use fetch and blob like this

    void fetch(downloadLink)
      .then(response => response.blob())
      .then(blob => {
        const blobUrl = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.style.display = 'none';
        open && link.setAttribute('target', '_blank');
        link.setAttribute('download', filename);
        link.setAttribute('href', blobUrl);
        link.click();
        URL.revokeObjectURL(blobUrl);
        setLoadingDownloadItem(prev => prev.filter(item => item !== index));
      });
  };
Rheo answered 6/12, 2023 at 13:50 Comment(0)
M
0

Although this issue has been around for a long time, recent I also meet this mutiple download problem,I want to do a summarize:

there has 2 way to accomplish this problem

1. new iframe or new _blank

like @mche anwser, we can use new iframe or new _blank to accomplish the problem, but we will create many iframe if we have many downloads, There is a risk of memory leaks

or like this, use new blank,this way will open many new _blank tab if we have many items to download

for (i = 0; i < photosURL.length; i++) {

    var downloadLink = document.createElement("a");
    downloadLink.target = "_blank"; // new tab
    downloadLink.href = photosURL[i];
    downloadLink.download = "foto_id_" + [i];

    document.body.appendChild(downloadLink);
    downloadLink.click();
    document.body.removeChild(downloadLink);

}

delay time to download

we set de dalay to download multiple file

const delay = milliseconds => new Promise(resolve => {
    setTimeout(resolve, milliseconds);
});

const download = async (url, name) => {
    const a = document.createElement('a');
    a.download = name;
    a.href = url;
    a.style.display = 'none';
    document.body.append(a);
    a.click();

    // Chrome requires the timeout
    await delay(100);
    a.remove();
};

export default async function multiDownload(urls, {rename} = {}) {
    if (!urls) {
        throw new Error('`urls` required');
    }

    for (const [index, url] of urls.entries()) {
        const name = typeof rename === 'function' ? rename({url, index, urls}) : '';

        await delay(index * 1000);
        download(url, name);
    }
}

we can use when we need download

multiDownload(urls)

However, this solution also has shortcomings in actual use. I find in some case ,when the first download request send, delay 1s, the server not responsed in 1s, and second requset be send will also cancel the first req

Merits answered 4/7 at 10:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.