Repository: influxdata/influxdb-client-js (ts)

Issue:
triggerUncaughtException after field type conflict
### Specifications

* Client Version: 1.30.0
* InfluxDB Version: 2.4.0
* Platform: client: MacOS and nodejs 18.2.0, server: docker container on synology

When using the writeApi interface to add points, when the auto flushing kicks in, the following error is thrown and my nodejs script exits:
```
WARN: Write to InfluxDB failed (attempt: 2). d [HttpError]: unexpected error writing points to database: field type conflict
    at IncomingMessage.<anonymous> (<hidden>/node_modules/@influxdata/influxdb-client/dist/index.js:5:5309)
    at IncomingMessage.emit (node:events:539:35)
    at endReadableNT (node:internal/streams/readable:1344:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  statusCode: 500,
  statusMessage: 'Internal Server Error',
  body: '{"code":"internal error","message":"unexpected error writing points to database: field type conflict"}',
  contentType: 'application/json; charset=utf-8',
  json: {
    code: 'internal error',
    message: 'unexpected error writing points to database: field type conflict'
  },
  code: 'internal error',
  _retryAfter: 0
}
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

d [HttpError]: unexpected error writing points to database: field type conflict
    at IncomingMessage.<anonymous> (<hidden>/node_modules/@influxdata/influxdb-client/dist/index.js:5:5309)
    at IncomingMessage.emit (node:events:539:35)
    at endReadableNT (node:internal/streams/readable:1344:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  statusCode: 500,
  statusMessage: 'Internal Server Error',
  body: '{"code":"internal error","message":"unexpected error writing points to database: field type conflict"}',
  contentType: 'application/json; charset=utf-8',
  json: {
    code: 'internal error',
    message: 'unexpected error writing points to database: field type conflict'
  },
  code: 'internal error',
  _retryAfter: 0
}
```

The question is not what I should do to solve the type conflict, but how to catch the error to prevent my code from exiting?


### Code sample to reproduce problem

My code listens to mqtt topics and based on a selector it writes data to influxdb. 


### Expected behavior

A way to catch the error that is thrown from the flush logic that gets automatically triggered based on the number of pending points or the time since the last write.

### Actual behavior

My code exits with this error message:

```
WARN: Write to InfluxDB failed (attempt: 2). d [HttpError]: unexpected error writing points to database: field type conflict
    at IncomingMessage.<anonymous> (<hidden>/node_modules/@influxdata/influxdb-client/dist/index.js:5:5309)
    at IncomingMessage.emit (node:events:539:35)
    at endReadableNT (node:internal/streams/readable:1344:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  statusCode: 500,
  statusMessage: 'Internal Server Error',
  body: '{"code":"internal error","message":"unexpected error writing points to database: field type conflict"}',
  contentType: 'application/json; charset=utf-8',
  json: {
    code: 'internal error',
    message: 'unexpected error writing points to database: field type conflict'
  },
  code: 'internal error',
  _retryAfter: 0
}
node:internal/process/promises:288
            triggerUncaughtException(err, true /* fromPromise */);
            ^

d [HttpError]: unexpected error writing points to database: field type conflict
    at IncomingMessage.<anonymous> (<hidden>/node_modules/@influxdata/influxdb-client/dist/index.js:5:5309)
    at IncomingMessage.emit (node:events:539:35)
    at endReadableNT (node:internal/streams/readable:1344:12)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  statusCode: 500,
  statusMessage: 'Internal Server Error',
  body: '{"code":"internal error","message":"unexpected error writing points to database: field type conflict"}',
  contentType: 'application/json; charset=utf-8',
  json: {
    code: 'internal error',
    message: 'unexpected error writing points to database: field type conflict'
  },
  code: 'internal error',
  _retryAfter: 0
}
```


### Additional info

_No response_

Pull request description:
fix(core): fix unhandled promise rejection in write retry
Fixes #593

## Proposed Changes
- write API and write retry tests were enhanced to check for unhandled promise rejections, because the unit test framework (mocha) silently swallows promise rejections. Unhandled promise rejections normally terminate node.js process (unless '
- retry buffer was fixed to silently consume write failures before re-scheduling it, it was this way correctly in 1.29 and changed in 1.30 in #547, the following lines were causing it: in https://github.com/influxdata/influxdb-client-js/pull/547/files#diff-16970e3a239345f536ebc0fa851bf7d9732aa1f22160fe5667a9e1bf7de42a52L100-L108

## Checklist

<!-- Checkboxes below this note can be erased if not applicable to your Pull Request. -->

- [x] CHANGELOG.md updated
- [x] Rebased/mergeable
- [x] A test has been added if appropriate
- [x] `yarn test` completes successfully
- [x] Commit messages are [conventional](https://www.conventionalcommits.org/en/v1.0.0/)
- [x] Sign [CLA](https://www.influxdata.com/legal/cla/) (if not already signed)


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3643ba2..5f90a32b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
 
 1. [#588](https://github.com/influxdata/influxdb-client-js/pull/588): Allow to pause/resume flux query result communication observer.
 
+### Bug Fixes
+
+1. [#604](https://github.com/influxdata/influxdb-client-js/pull/604): Fix unhandled promise rejection in write retry.
+
 ## 1.30.0 [2022-09-29]
 
 ### Features
diff --git a/packages/core/src/impl/RetryBuffer.ts b/packages/core/src/impl/RetryBuffer.ts
index 678e2494..b2a2eba3 100644
--- a/packages/core/src/impl/RetryBuffer.ts
+++ b/packages/core/src/impl/RetryBuffer.ts
@@ -130,16 +130,16 @@ export default class RetryBuffer {
     this._timeoutHandle = setTimeout(() => {
       const toRetry = this.removeLines()
       if (toRetry) {
-        this.retryLines(
-          toRetry.lines,
-          toRetry.retryCount,
-          toRetry.expires
-        ).finally(() => {
-          // schedule next retry execution
-          if (this.first) {
-            this.scheduleRetry(this.first.retryTime - Date.now())
-          }
-        })
+        this.retryLines(toRetry.lines, toRetry.retryCount, toRetry.expires)
+          .catch(() => {
+            /* error is already logged, it must be caught */
+          })
+          .finally(() => {
+            // schedule next retry execution
+            if (this.first) {
+              this.scheduleRetry(this.first.retryTime - Date.now())
+            }
+          })
       } else {
         this._timeoutHandle = undefined
       }


Test patch:
diff --git a/packages/core/test/unit/WriteApi.test.ts b/packages/core/test/unit/WriteApi.test.ts
index a32b22bd..fe30bd95 100644
--- a/packages/core/test/unit/WriteApi.test.ts
+++ b/packages/core/test/unit/WriteApi.test.ts
@@ -11,7 +11,7 @@ import {
   DEFAULT_WriteOptions,
   PointSettings,
 } from '../../src'
-import {collectLogging, CollectedLogs} from '../util'
+import {collectLogging, CollectedLogs, unhandledRejections} from '../util'
 import {Log} from '../../src/util/logger'
 import {waitForCondition} from './util/waitForCondition'
 import zlib from 'zlib'
@@ -61,10 +61,12 @@ function createWriteCounters(): WriteListeners {
 describe('WriteApi', () => {
   beforeEach(() => {
     nock.disableNetConnect()
+    unhandledRejections.before()
   })
   afterEach(() => {
     nock.cleanAll()
     nock.enableNetConnect()
+    unhandledRejections.after()
   })
   describe('simple', () => {
     let subject: WriteApi
diff --git a/packages/core/test/unit/impl/RetryBuffer.test.ts b/packages/core/test/unit/impl/RetryBuffer.test.ts
index 5fc314bd..750340ce 100644
--- a/packages/core/test/unit/impl/RetryBuffer.test.ts
+++ b/packages/core/test/unit/impl/RetryBuffer.test.ts
@@ -1,6 +1,6 @@
 import {expect} from 'chai'
 import RetryBuffer from '../../../src/impl/RetryBuffer'
-import {CollectedLogs, collectLogging} from '../../util'
+import {CollectedLogs, collectLogging, unhandledRejections} from '../../util'
 import {waitForCondition} from '../util/waitForCondition'
 
 describe('RetryBuffer', () => {
@@ -8,9 +8,11 @@ describe('RetryBuffer', () => {
   beforeEach(() => {
     logs = collectLogging.decorate()
     // logs = collectLogging.replace()
+    unhandledRejections.before()
   })
   afterEach(async () => {
     collectLogging.after()
+    unhandledRejections.after()
   })
   it('stores lines for future retry', async () => {
     const input = [] as Array<[string[], number]>
diff --git a/packages/core/test/util.ts b/packages/core/test/util.ts
index 3911f159..7d06ba50 100644
--- a/packages/core/test/util.ts
+++ b/packages/core/test/util.ts
@@ -1,3 +1,4 @@
+import {expect} from 'chai'
 import {setLogger} from '../src/util/logger'
 
 let previous: any
@@ -47,3 +48,22 @@ export const collectLogging = {
     }
   },
 }
+
+let rejections: Array<any> = []
+function addRejection(e: any) {
+  rejections.push(e)
+}
+
+/**
+ * Used by unit tests to check that no unhandled promise rejection occurs.
+ */
+export const unhandledRejections = {
+  before(): void {
+    rejections = []
+    process.on('unhandledRejection', addRejection)
+  },
+  after(): void {
+    process.off('unhandledRejection', addRejection)
+    expect(rejections, 'Unhandled Promise rejections detected').deep.equals([])
+  },
+}