Skip to content

Stream write callback doesn't allow handling errors #35

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

Closed
wclr opened this issue Jun 26, 2021 · 9 comments · Fixed by #41 or #43
Closed

Stream write callback doesn't allow handling errors #35

wclr opened this issue Jun 26, 2021 · 9 comments · Fixed by #41 or #43
Labels
purs-0.15 A reminder to address this issue or merge this PR before we release PureScript v0.15.0 type: breaking change A change that requires a major version bump. type: enhancement A new feature or addition.

Comments

@wclr
Copy link

wclr commented Jun 26, 2021

foreign import writeStringImpl
  :: forall r
   . Writable r
  -> String
  -> String
  -> Effect Unit -- callback should allow error handling
  -> Effect Boolean

The callback should allow error handling, for example:
(Nullable Error) -> Effect Unit though the error value passed to the callback may be undefined or js Error.

@JordanMartinez JordanMartinez added purs-0.15 A reminder to address this issue or merge this PR before we release PureScript v0.15.0 type: breaking change A change that requires a major version bump. type: enhancement A new feature or addition. labels Dec 7, 2021
@JordanMartinez
Copy link
Contributor

JordanMartinez commented Apr 28, 2022

See #41 (comment)

@JordanMartinez
Copy link
Contributor

JordanMartinez commented Apr 28, 2022

@wclr Can you provide a Node.js example that demonstrates a time where the arg will be undefined? The docs don't really clarify this, so I assumed it was always Error and forgot about the null possibility.

@thomashoneyman I think we'll need to fix this before 0.15.0 is released. Our FFI bindings cannot have wrong type signatures.

@wclr
Copy link
Author

wclr commented Apr 28, 2022

Can you provide a Node.js example that demonstrates a time where the arg will be undefined?

It is undefined in v16.13.2:

const fs = require("fs")
var writeStream = fs.createWriteStream("./file.txt")
writeStream.write("some", null, (err) => {
  console.log("err", err) // prints out `undefined`
  writeStream.end((endErr) => {
       console.log("endErr", endErr) // prints out `undefined`
   })
})  

So to use Nullable that means that the implementation:

export function write(w) {
  return chunk => done => () => w.write(chunk, null, done);
}

Should be changed to:

export function write(w) {
  return chunk => done => () => w.write(chunk, null, (err) => done(err || null));
}

The same for writeStringImpl and end (this too may give the error, for example if try to end already finished stream) foreing implementations.

Other approach instread of Nullable use two callbacks (success and onError), though this kind of different api.

@JordanMartinez
Copy link
Contributor

Thanks. I can confirm that. Idiomatically, it would be better if the API exposed to the end-user was Maybe Error -> Effect Unit.

@wclr
Copy link
Author

wclr commented Apr 29, 2022

It would be better if the API exposed to the end-user was Maybe Error -> Effect Unit.

I think this is right, I just usually try to make FFI code as simple as possible.

@JordanMartinez
Copy link
Contributor

My thought was that if we used Nullable Error, all end users would have to write something like this:

-- We want to handle both cases
\err -> do
  case toMaybe err of
    Nothing -> ...
    Just e -> ...

-- We only care if it's an actual error:
\err -> for_ (toMaybe err) \actualErr -> ...

-- We only care if it's not an error:
\err -> when (null == err) $ ...

Since 2/3 use toMaybe and the third could be written in terms of Maybe (e.g. when (isNothing err) $ ...), we could implement the FFI like this:

foreign import writeStringImpl
  :: forall r. Writable r -> String -> String -> (Nullable Error -> Effect Unit) -> Effect Boolean

writeString
  :: forall r. Writable r -> String -> String -> (Maybe Error -> Effect Unit) -> Effect Boolean
writeString w s1 s2 cb = writeStringImpl w s1 s2 (cb <<< toMaybe)

@JordanMartinez
Copy link
Contributor

Should be changed to

export function write(w) {
 return chunk => done => () => w.write(chunk, null, (err) => done(err || null));
}

Not quite. The err || null part isn't needed. toMaybe is defined as:

toMaybe :: forall a. Nullable a -> Maybe a
toMaybe n = runFn3 nullable n Nothing Just

nullable is implemented as:

export function nullable(a, r, f) {
  return a == null ? r : f(a);
}

and running nullable(undefined, true, () => false) returns true.

If nullable was implemented as a === null rather than a == null, then we'd need the err || null part.

@garyb
Copy link
Member

garyb commented Apr 29, 2022

We do tend to use Maybes rather than Nullable in the web libraries, as it is nicer to deal with for consumers.

@JordanMartinez
Copy link
Contributor

The same for ... end (this too may give the error, for example if try to end already finished stream) foreign implementations.

Yeah, I can confirm this too with this example:

import fs from "fs";

var file = fs.createWriteStream("./file.txt")
file.on('error', () => {});
file.write("something", () => {
  file.destroy(new Error("Muahahah!"));
  file.end((err) => {
    console.log("Example where 'err' is Error");
    console.log(err)
  });

  var file2 = fs.createWriteStream("./file2.txt")
  file2.end((err) => {
    console.log("Example where 'err' is undefined");
    console.log(err);
  });
});

which outputs:

Example where 'err' is Error
Error [ERR_STREAM_DESTROYED]: Cannot call end after a stream was destroyed
    at new NodeError (node:internal/errors:371:5)
    at WriteStream.Writable.end (node:internal/streams/writable:636:11)
    at file:///home/user/z.mjs:7:8
    at afterWrite (node:internal/streams/writable:497:5)
    at onwrite (node:internal/streams/writable:477:7)
    at node:internal/fs/streams:410:5
    at FSReqCallback.wrapper [as oncomplete] (node:fs:801:5) {
  code: 'ERR_STREAM_DESTROYED'
}
Example where 'err' is undefined
undefined

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
purs-0.15 A reminder to address this issue or merge this PR before we release PureScript v0.15.0 type: breaking change A change that requires a major version bump. type: enhancement A new feature or addition.
Projects
None yet
3 participants