Home > Mobile >  Express.js - listen to request aborted
Express.js - listen to request aborted

Time:09-29

I use got.stream to download a file and send it to the client with streaming.

I want to have a monitor of how many downloads there are at any given moment, and for that I need to know when the download ends in order to update the counter.

const stream = require('stream');
const { promisify } = require('util');
const pipeline = promisify(stream.pipeline);
const got = require('got');


let currentDownloadsStream = 0;

app.get('download', async (req, res) => {
    currentDownloadsStream  ;
    const downloader = got.stream(url);
    return pipeline(downloader, res)
        .finally(() => currentDownloadsStream--)
});

The problem is that if the client canceled the download in the middle, the finally method is never called, it seems as if the promise stays in memory forever and is never resolved.

I tried to add

res.on('close', () => {
    downloader.destroy();
})

But that didn't help either, if I cancel the download in the browser this event doesn't fire.

How can I tell if the client stopped the download before it finished?

nodejs v16.17.0


Edit: Thanks to @jfriend00 for helping me discover the problem, the problem is caused by the webpack development server, in production the event is emitted normally.

CodePudding user response:

The problem is that if the client canceled the download in the middle, the finally method is never called, it seems as if the promise stays in memory forever and is never resolved.

If the browser download is really cancelled, I am unable to reproduce this problem in Chrome or Edge and nodejs version 16.14.2. When I start a huge download and I cancel the download by clicking the "Cancel" option in the download bar in Chrome, I immediately get a bunch of events and see the pipeline promise reject and call .finally().

I'm wondering if you aren't actually cancelling the download in the browser. In both Chrome and Edge (I didn't try other browsers), just closing the tab or window of the download does not stop the download. You have to either physically cancel the download or exit the entire browser and answer "yes" to a prompt to stop the download.

Here are the events I get when manually cancelling the download in Chrome and Edge:

  • close event on res stream
  • error event on downloader: ERR_STREAM_PREMATURE_CLOSE
  • close event on the downloader stream
  • .catch() on the pipeline promise: ERR_STREAM_PREMATURE_CLOSE
  • .finally() on the pipeline promise
  • error event on the downloader stream: ECONNRESET

In the Edge browser, if I completely exit the browser and answer "yes" to the prompt to cancel the "in progress" download, then I get:

  • close event on res stream
  • error event on downloader: ERR_STREAM_PREMATURE_CLOSE
  • close event on downloader stream
  • .catch() on the pipeline promise
  • .finally() on the pipeline promise
  • error event on downloader stream: ECONNRESET

FYI, I used this URL for a large download: "http://speedtest-sgp1.digitalocean.com/5gb.test" from this site: https://testfiledownload.com/ as the source for a large download.

Here's my test code:

const express = require('express');
const stream = require('stream');
const { pipeline } = require('node:stream/promises');
const got = require('got');

const app = express();

const url = "http://speedtest-sgp1.digitalocean.com/5gb.test";


let currentDownloadsStream = 0;

app.get('/download', async (req, res) => {
    currentDownloadsStream  ;
    const downloader = got.stream(url);

    downloader.on('end', () => {
        console.log("end event on downloader");
    }).on('close', () => {
        console.log("close event on downloader");
    }).on('error', err => {
        console.log("error event on downloader");
        console.log(err);
    });

    res.on('finish', () => {
        console.log("finish event");
    }).on('end', () => {
        console.log("end event on res");
    }).on('prefinish', () => {
        console.log("prefinish eventon res");
    }).on('close', () => {
        console.log("close event on res");
    }).on('error', err => {
        console.log("error event on res");
        console.log(err);
    });

    pipeline(downloader, res).then(result => {
        console.log(".then() called");
    }).catch(err => {
        console.log(".catch() called");
        console.log(err);
    }).finally(() => {
        console.log(".finally() called");
        currentDownloadsStream--;
    })
});

app.listen(80);
  • Related