Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error event is emitted before every try even if download finished successfully #57

Closed
Envek opened this issue May 22, 2021 · 8 comments
Closed
Assignees
Labels
bug Something isn't working

Comments

@Envek
Copy link

Envek commented May 22, 2021

Hey, thank you for this package, it helps.

However, I found some confusing behavior that I believe is a bug. error event is emitted after every unsuccessful try even if download was successful.

In my understanding of EventEmitter errors, error event is (usually) a fatal one: it can't be recovered and you need to restart everything from scratch.

For example:

const { DownloaderHelper } = require("node-downloader-helper")

(async () => {
  const dl = new DownloaderHelper(
    "https://example.com/",
    ".", {
      fileName: "anything",
      retry: { maxRetries: 3, delay: 50 },
    }
  );
  dl.on("retry", console.debug);
  dl.on("error", console.error);
  dl.on("end", console.info);
  await dl.start();
})()

Suppose that network error occurs for first two times, but on third try file downloads successfully.

Expected behavior:

error event isn't fired if download was successful after all.

DEBUG: 1 { maxRetries: 3, delay: 50 }
DEBUG: 2 { maxRetries: 3, delay: 50 }
INFO:  {  fileName: 'anything', … , downloadedSize: 1256 }

Actual behavior:

error event is emitted before every try.

ERROR: Error: ECONNRESET
DEBUG: 1 { maxRetries: 3, delay: 50 }
ERROR: Error: ECONNRESET
DEBUG: 2 { maxRetries: 3, delay: 50 }
INFO:  {  fileName: 'anything', … , downloadedSize: 1256 }
@Chaphasilor
Copy link
Contributor

Hey, as far as I understand it the error event doesn't have to be a fatal, non-recoverable error.
However, if you want to catch fatal errors, do it by catching the promise of the dl.start() method!

this.dl.start().catch(err => {
  
  this.status = `failed`;
  this.emit(`failed`, err);
  return;
  
});

Hope this helps :)

@Envek
Copy link
Author

Envek commented May 26, 2021

No, error events by default are fatal, see Events: Error events in the Node.js docs:

If an EventEmitter does not have at least one listener registered for the 'error' event, and an 'error' event is emitted, the error is thrown, a stack trace is printed, and the Node.js process exits.

So, if I run following code in node --eval (not in REPL, it is important):

const { DownloaderHelper } = require("node-downloader-helper");
(async () => {
  const dl = new DownloaderHelper(
    "https://nonexist.example.com",
    ".",
    { fileName: "anything", retry: { maxRetries: 3, delay: 50 } },
  );
  dl.on("retry", console.debug);
  dl.on("end", console.info);
  try {
    await dl.start()
  } catch (e) {
    throw new Error(`I GIVE UP! ${e.message}`)
  }
})()                   

Expected result:

3 retries are performed, custom error is thrown:

1 { maxRetries: 3, delay: 50 }
2 { maxRetries: 3, delay: 50 }
3 { maxRetries: 3, delay: 50 }
(node:23020) UnhandledPromiseRejectionWarning: Error: I GIVE UP! getaddrinfo ENOTFOUND nonexist.example.com

Actual result:

No retries performed, original error is thrown:

events.js:292
      throw er; // Unhandled 'error' event
      ^

Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26)
Emitted 'error' event on b instance at:
    at ClientRequest.<anonymous> (/home/envek/evl.ms/2uinc/ext-admissions-workflow/node_modules/node-downloader-helper/dist/index.js:1:10025)

Workaround:

Always add some dummy error handler:

dl.on("error", () => {});

@hgouveia hgouveia self-assigned this Jun 10, 2021
hgouveia added a commit that referenced this issue Aug 19, 2021
@hgouveia hgouveia added the bug Something isn't working label Aug 19, 2021
@hgouveia
Copy link
Owner

Hello @Envek , i just added a fix for this, i wonder if you could try it by install it like this npm install --save hgouveia/node-downloader-helper#dev , and check if works as expected now before merge with master, thanks!

@Envek
Copy link
Author

Envek commented Aug 20, 2021

Now it performs 3 retries as expected, but then it emits error event 4 times (looks like one for every retry and then one “final”), while I would expect only one error emit after all retries.

Now it works almost as expected, thank you!

Here is runnable example and its output

You can paste it right into bash or zsh shell:

xargs -0 <<'JS' node --eval 
const { DownloaderHelper } = require("node-downloader-helper");
(async () => {
  const dl = new DownloaderHelper(
    "https://nonexist.example.com",
    ".",
    { fileName: "anything", retry: { maxRetries: 3, delay: 50 } },
  )
  dl.on("retry", (...args) => console.debug("RETRY: ", ...args))
  dl.on("end", (...args) => console.info("COMPLETED: ", ...args))
  dl.on('error', (e) => console.error("ERROR EMITTED: ", e))
  try {
    await dl.start()
  } catch (e) {
    console.error(`I GIVE UP\! ${e.message}`)
  }
})()
JS

Output:

RETRY:  1 { maxRetries: 3, delay: 50 } Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
RETRY:  2 { maxRetries: 3, delay: 50 } Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
RETRY:  3 { maxRetries: 3, delay: 50 } Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
ERROR EMITTED:  Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
ERROR EMITTED:  Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
ERROR EMITTED:  Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
ERROR EMITTED:  Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
I GIVE UP! getaddrinfo ENOTFOUND nonexist.example.com

@Chaphasilor
Copy link
Contributor

Seems to happen because of this change

My guess is the .catch() handler is chained multiple times and after the last retry fails, all (3) catch handlers are invoked, emitting the error event three times...

@hgouveia
Copy link
Owner

Hello @Envek i just did a new fix, could you try once again?

@Envek
Copy link
Author

Envek commented Aug 23, 2021

@hgouveia, now it emits error event only once. Thank you!

Output of the same runnable example on fresh dev branch
RETRY:  1 { maxRetries: 3, delay: 50 } Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
RETRY:  2 { maxRetries: 3, delay: 50 } Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
RETRY:  3 { maxRetries: 3, delay: 50 } Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
ERROR EMITTED:  Error: getaddrinfo ENOTFOUND nonexist.example.com
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:67:26) {
  errno: -3008,
  code: 'ENOTFOUND',
  syscall: 'getaddrinfo',
  hostname: 'nonexist.example.com'
}
I GIVE UP! getaddrinfo ENOTFOUND nonexist.example.com

hgouveia added a commit that referenced this issue Oct 4, 2021
commit 33aab44
Author: Jose De Gouveia <dhgouveia@gmail.com>
Date:   Mon Oct 4 11:12:26 2021 +0200

    Incremented 1.0.19

commit ac8387f
Author: Jose De Gouveia <dhgouveia@gmail.com>
Date:   Tue Aug 24 17:43:35 2021 +0200

    Fixed attempt to ECONNRESET at TLSWrap.onStreamRead #61

commit 305676e
Author: Jose De Gouveia <dhgouveia@gmail.com>
Date:   Tue Aug 24 14:42:20 2021 +0200

    Fixed raise error event when paused in newer nodejs versions #62

commit 9d1bd9c
Author: Jose De Gouveia <dhgouveia@gmail.com>
Date:   Fri Aug 20 16:44:12 2021 +0200

    Fixed muiltiple 'error' emits when retry #57

commit e8c00d8
Author: Jose De Gouveia <dhgouveia@gmail.com>
Date:   Thu Aug 19 16:44:57 2021 +0200

    Fixed error emitting when retry #57

commit bc20b86
Author: Jose De Gouveia <dhgouveia@gmail.com>
Date:   Thu Aug 19 14:51:48 2021 +0200

    When timeout, it will retry the download if available if not emit timeout #40

commit 1306bc8
Author: Henrique Bruno Fantauzzi de Almeida <henrique.bruno.fa@gmail.com>
Date:   Tue Aug 17 04:42:25 2021 -0300

    fileName {ext} type now also optional and boolean (#51)

    This fixes the typing for fileName property, now accepting:

    `fileName: {name: 'xyz'}`
    `fileName: {name: 'xyz', ext: true}` or false, stating false is the default
    `fileName: {name: 'xyz', ext: 'jpg'}` as already was accepted

commit bed68d5
Author: Chaphasilor <ppp.u@web.de>
Date:   Tue Aug 17 09:41:05 2021 +0200

    Fix leading dots being removed (#59)

    * fix leading dots being removed

    * only modify auto-generated filenames + added test
@hgouveia
Copy link
Owner

hgouveia commented Oct 4, 2021

@Envek published to npm v1.0.19

@hgouveia hgouveia closed this as completed Oct 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants