thi-ng / umbrella

⛱ Broadly scoped ecosystem & mono-repository of 199 TypeScript projects (and ~180 examples) for general purpose, functional, data driven development
https://thi.ng
Apache License 2.0
3.39k stars 150 forks source link

[Bug][hdom, rstream] error is silently ignored #125

Open nkint opened 5 years ago

nkint commented 5 years ago

Hi! I don't actually know which combination of hdom, rstream and generators causes this issue but in this particular case if I have some error it is not shown and it vanishes into the void causing troubleshooting and debugging quite difficult.

I'll try to investigate on it but maybe someone has a smart enlightenment.

import { stream, sync } from '@thi.ng/rstream'
import { updateDOM } from '@thi.ng/transducers-hdom'
import { map, scan, count, range } from '@thi.ng/transducers'

function getColor(n: number, clicks: number) {
  if (n === 3) {
    throw new Error('dudee')
  }
  return n === clicks - 1 ? 'red' : 'black'
}

const app = ({ clicks }: any) => [
  'div',
  [
    'a',
    {
      href: '#',
      onclick: () => clickStream.next(0),
    },
    `${clicks} clicks`,
  ],
  ...[...range(clicks)].map(n => {
    console.log(n, clicks - 1)
    return ['p', { style: { color: getColor(n, clicks) } }, n]
  }),
]

const clickStream = stream().transform(scan(count(-1)))
clickStream.next(0)

sync<any, any>({
  src: {
    clicks: clickStream,
  },
}).transform(map(app), updateDOM({ root: document.body }))
postspectacular commented 5 years ago

Ciao, @nkint - am back from the mountains... will look into this tomorrow, but 2 things:

  1. currently there's no explicit error handling (yet!) for transform()ers (transducers) - this will be added as part of the ongoing updates on the feature/rstream-opts branch
  2. you can enable error logging like this:
import { ConsoleLogger, LogLevel } from "@thi.ng/api";
import { setLogger } from "@thi.ng/rstream";

// set package specific logger instance
setLogger(new ConsoleLogger("rstream", LogLevel.WARN));

Be aware that not all errors will be caught & logged via this mechanism, but it should work for the above use case...

In general, each ISubscriber can provide an error hook, but transducers added via transform() are handled differently, and as mentioned, currently still lack user controllable error handling/hooks...

postspectacular commented 5 years ago

@nkint Just to add to this, here's how you can add/override an error handler for a transform() subscription (based on your example)

const main = sync<any, any>({
    src: {
        clicks: clickStream
    }
});
const sub = main.transform(map(app), updateDOM({ root: document.body }));
sub.error = (e) => alert(e.message);
postspectacular commented 5 years ago

The other thing worth noting here is that iff a custom error handler is provided, the subscription will NOT automatically go into the ERROR state and can receive future values. Only if the default error handler is called, the sub switches to that state and silently ignores further inputs... This behavior still needs to be documented. But the underlying reasoning for this is that you could do something like:

import { fromIterable, stream, sync } from "@thi.ng/rstream";
import { map, range, sideEffect } from "@thi.ng/transducers";
import { updateDOM } from "@thi.ng/transducers-hdom";

const app = ({ mainXform, error }: any) => [
    "div",
    ["h1", mainXform],
    error && ["div.red", "error: ", error]
];

const main = fromIterable(range(5), 1000);

const error = stream<string | null>();
error.next(null);

const mainXform = main.transform(
    sideEffect((x: number) => {
        if (x > 2) throw new Error(String(x));
    }),
    map((x: number) => x * 10)
);
mainXform.error = (e: Error) => error.next(e.message);

sync<any, any>({
    src: { mainXform, error }
}).transform(map(app), updateDOM());