-
Notifications
You must be signed in to change notification settings - Fork 4
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
Discuss some design detail #7
Comments
It sure is. Seems like you don't have a trampoline though - I find it very convenient that I can write infinitely recursive functions with Bluebird without blowing the stack, without even thinking about it. Monadic actions/futures can be quite beautiful: here for example is @robotlolita 's wonderfully minimal Future library, translated to only a few lines of ES6: https://gist.github.com/spion/b433a665ca51d6c38e9f#file-3-reference-implementation-in-es6-js (See the link below the fork for the original) I wasn't criticising your library's design choices per-se: assuming that we had a "safe prelude" (one where But we do have exceptions in JavaScript and node. Lots of functions throw exceptions for all kinds of reasons - its not like we only have the automatic TypeError and ReferenceError for trying to call or dereference undefined. We have all kinds of functions that throw because they don't like their input. We have a throwing, stateful JS prelude. Because of this, I don't really want to use a library that pretends exceptions don't exist. Additionally because of pervasive state mutation, I want a library that has execution order guarantees, i.e. I like that promises guarantee that any fulfilment or error handlers attached to them will run after the currently executing function finishes. Finally, JavaScript is Promises are not meant to be monads. They are meant to fix the fact that synchronous JS constructs become unusable once you switch to async code. Thats why they catch all exceptions - to continue their propagation up the "async" stack, just as they would propagate if the stack was synchronous. Promises replace variables, Promises are just JavaScript, and thats the reason why they were added to ES6 - they are a good fit. |
First thank you for reviewing my code : ), you asked some good questions about the design, some because Action.js was designed to be more browser friendly, some for other reasons. i would like to explain it more, but i have limited knowledge on promise internal, so please take these with a grain of salt.
That will involve Action.repeat(-1,
Action.delay(0,
new Action((cb)=>{
console.log('This will be repeated forever without blow your stack!');
return cb();
})
)
)
I choose coffee because it's easier to setup than babel LOL, maybe it's time to switch to ES6, i don't know. But coffee happily compile everything to es3, which i want to support now.
That's actually what Promise upset me most, it replace someAction
.next(function(data){
try{
return unsafeProcess(data);
}
catch(e){
return e;
}
})
.next(...)
.guard(...)
.go() It's not pretending exceptions don't exist, it's just didn't change the way how
That's the most convincing argument about why to use Promise so far, because its focus on value rather than actions. so basically people can use it like normal value. But consider this, once we want to add some control structures on top of Promise, for example retry/throttle/error-recovery. we are forced to work on functions which produce Promise instead of Promise itself, and these control structures are become much expensive because we have to instantiate new Promises to make the action happen. customized control structure is always tend to favour lazy execution.
If Promise is built into core, i think i will add following isomorphism to take advantage of that : ) fromPromise = (p) =>
new Action((cb) => p.then(cb, cb)) BTW, have you checked this project's wiki? It records some thoughts on async nature, and you're very welcomed to review it. |
And so is
The thing is, asynchronous code already changes the way exceptions work: They don't bubble any more. So if you don't change them back, you have effectively changed the intended behaviour of the language. All that promises do is make JS exceptions bubble again, from the last "semicolon" ( |
I have no trouble with the choice of coffeescript, just wanted to say that I admire the simplicity and elegance of lazy monadic actions. |
That's the deal breaker everyone talked about, some people like you enjoy it, while some people like me can't accept it ; ) I also hope that await sync syntax can be used with any async primitive with a monadic interface. |
Still, I haven't seen a very convincing argument against it. And also, the node people who say its a deal breaker still write code in node core that deliberately throws exceptions :) Anyways, most downsides of exceptions are avoided by avoiding / being careful with shared mutable state. The few JS-specific downsides are avoidable by adding a typechecker (flowtype/typescript/closure)
Unfortunately, no. I think async/await is a mistake, and, it being unusable with anything else except for promises is another good reason why. |
I want to throw some points here, but maybe all these has been discussed before : )
newtype ExceptT e m a = ExceptT (m (Either e a)) it's a instance (Monad m) => Monad (ExceptT e m) where
...
m >>= k = ExceptT $ do
a <- runExceptT m
case a of
Left e -> return (Left e)
Right x -> runExceptT (k x) And Action.js provide Action.prototype.guard = function(cb) {
return new Action((_cb) =>
this._go(function(data){
if (data instanceof Error) {
var _data = cb(data);
if (_data instanceof Action) {
return _data._go(_cb);
} else {
return _cb(_data);
}
} else {
return _cb(data);
}
});
);
}; AFAICT this approach provide a nice interface while doesn't affect origin
I complete agree with you on this matter, since Check out my |
It does so by ignoring reality. The reality is that I don't want to use this because if I do, my node programs will crash. If used in a web service, when a user finds an endpoint that will crash, they can refresh 8 times quickly and kill all my workers. When those workers crash they will at the same time destroy the work of hundreds of other users that are currently connected to them. Suggested reading: nodejs/node-v0.x-archive#4583 |
Let's say we haven't consider some dangerous operation which may throw: //with Promise
...
.then((data) => dangerousFun(data))
...
.catch((err) => rescue(err)) // It doesn't consider above error
//with Action
...
.next((data) => dangerousFun(data))
...
.guard((err) => rescue(err)) // It doesn't consider above error
.go() With Actions, yes when dangerousFun throws, it crash the process and report call stack/code position immediately, but that's intended behaviour. It just successfully report a bug IMO, and i can add a But with Promise, you never see what dangerousFun throw, it may leave your program in a unknown state without any sign. In fact if you use Promise, the issues you linked may be not found yet. In most cases, you don't even know what kind of errors you have to catch in Promise chain, because Promises come from other's module are not in your control, i prefer program crash and report in that cases, but that's my personal opinion. Since Promise community seems to be ok with this consumer decide what can be handled scheme, and prefer not to crash program, i would say this is just a personal taste : ) ,i just prefer not enclose every function with an implicit |
How do you deal with that being a potential denial of service attack vector?
Bluebird outputs unhandled rejections to stderr by default, and additionally has long stack traces built in.
Only if I am careless with shared mutable state and resources. Which btw can happen with anything else too: https://gist.github.com/spion/60e852c4f0fff929b894 - having thrown errors crash the process doesn't help there much I'm afraid. The source of the problem is shared mutable state, not exceptions.
Which is why you let them bubble up until you know how to handle those cases, but clean up resources on their way using
We cannot accept the possibility of having denial of service and one user affecting thousands of others. But we acknowledge that its not very easy to write code that doesn't leave the program in a bad state after an exception is thrown. Thats why we offer features that help with this, and we're working on others that make safe code more pleasant to write The issue here isn't whether exceptions are broken or not: yes they are somewhat broken. The issue is that using |
Just like any other language with
In that example, it will not, because you do add a //with Promise
...
.then((data) => dangerousFun(data)) // when it throw, what happened next? no idea...
...
.catch((err) => rescue(err)) // It doesn't consider above error BTW. this's very common in a large codebase with lot of people worked on, errors bubbled up from their Promises'
Use throw warning others with caution, if they don't debugger will yell with detail, we do have QA so this is something we can accept. |
Since the fundamental problem lies on Promise's implicit |
@spion, can we continue the discussion here? This repo is a experiment on how to separate monadic async interface and error handling in general. It basically a effort to achieve following haskell monad stack:
But i have to modify a lot of things since in javascript we really don't have algebra types and type classes. But i suppose the result so far is quite good, i get something quite usable, and by benchmark(it's based on your code isn't it?) it's faster than any promise library.
The text was updated successfully, but these errors were encountered: