Maybe we can all agree that experience in other languages leaves ECMAScript with much to be desired. Whether the changes ES6 hath wrought and ES7 and friends portend bode ill or well is likewise up for debate. So are the one-trueness of futures, specialized async syntax, and static typing. Those aren't either-or kinds of debates as far as ECMAScript is concerned. ECMAScript could be more Scheme-like, more Java-like, more ML-like. I've forgotten to mention some other disappointed camp. Forgive me!
If specific judgments on those kinds of controversies unify a culture, there is no unified culture of Node.js, any more than there is a unified culture of C or Java or English punctuation. The only unity is that standards-compliant ECMAScript runtimes are everywhere and we all want to use them. As long as different tastes are at least sufficiently well specified, we've a fighting chance of automating adapters. So I'd argue promisifiers and depromisifiers might be the most important libraries of the moment, culturally speaking. But it's too much to ask of any broad and adaptable common platform or library to hide the fact that tastes and approaches differ.
I have a few reasons why node-style callbacks are absolutely the worst possible choice, so I'd like to compare.
Here are some:
1. Not values. Values can participate in expressions, can be passed as arguments to functions, can be returned as values from functions, can be stored in variables, arrays, etc. There are no values with node-style callbacks. You are throwing away half of the language capabilities regarding values and expressions.
2. No guarantees. Callback may be called multiple times. It may be called synchronously or asynchronously. There is no way to know what will happen.
3. Bad semantics for thrown errors. Its assumed that thrown errors will simply crash the process. This is a dealbreaker in node because every uncaught thrown error provides a vector for denial of service attacks, and the thrown error semantics of most libraries and the standard node library are pretty bad.
I know most people use Promises to escape the call-back hell, but you can do that with async library as well, so maybe you just like writing "return cb(err);" more than "return Promise.reject(err);"
Maybe you like, that you need to deal with the err in the call-back, while \w promises it is easy to write code that just silently fails?
- They are simple (fully transparent)
- They are the simplest good enough solution for many problems
- Cleaner code than new Promise().then().then().then().catch() clutter
- Yes, I like nested indentations (good functions are short anyway)
- They perform well
As a side note, I also prefer coffeescript, snake_case, CONSTANTS, and avoid using classes, prototypes, and this. I'm not very happy with the recent development of EcmaScript, too much complexity and unnecessary new concepts.And I'd like to know what you mean by "fully transparent"
Fixed that for you.
Maybe he was right all along, but was actually talking about scheme.
(I can't just let go that it was soooo close that we would not had to suffer in the javascript wastelands.)
Thats also one cool thing of the js/node community. We just adopt stuff and try it out. With microservices, this also poses no problem. If it doesn't work, do it differently in the next service.
[1] I know, I know. A+ Promises are violating some laws BUT the general idea holds. And there are full monadic promise libraries like Data.Task or Fluture available.
If you want promises, just embrace promisify. I get a lot of this fear about error-first callbacks being only a "convention", but everything follows this convention in practice. Anything that can't be promisified because of that will likely have a pull request or a more popular fork that can.
I don't think node core libraries should be promise-based, and you can't expect the whole ecosystem to switch to promises if node core won't. At some point you have to jump to callback-style to live on node.
I also don't really understand what this post has to do with the release.
I don't see how compile-time type checking would help here. When checking out a new library, I read the documentation, or maybe it's test suite, and then use it accordingly. My tests ensure that I use it correctly, i.e. give it types it understands, give it the right data (the id of the correct user account for example), and handle the outcome correctly (i.e. display it in the right format).
JavaScript has run-time type checking, which could be used for automatic promisification, but I agree that's a slippery road, for many reasons.
Also you slipped in weasel language to disrespect JS developers. I have been coding for 30 years in wverything from assembly to C++ to C# to OCaml to now for the last several years mainly Node.js. Your comment was (reading between the lines) as close as you could get on Hacker News to spitting in my face.
Promisifying works fine. So do rewrites, as the language has evolved rapidly.
We do not actually have a big backwards compatibility problem.
Dynamic languages are dynamic.
We need something like promises to replace node streams too. We need streams with well defined semantics. Quick, what are the unpipe semantics for stream erors? Does a stream unpipe from its source? Will it unpipe synchronously or asynchronously? Can a stream emit an error synchronously when its created? At the next tick of the event loop? What about at the end of the same tick? Are these semantics defined anywhere? Which versions of node streams adhere to them? All is fuzzy.
We need to replace node streams and of all eventemitter-based streamlike stuff, badly.
Regarding types, you always have the option to add TypeScript into the mix.
It's all good for prototyping, but man, once you're up in production, dynamism is a recipe for pain. Granted, statically typed languages crash too, but the big difference is that you can often eliminate the same problem throughout your program by getting the type right, as opposed to trying to TDD bugs one at a time as they emerge.
I think you nailed it when you said, "there's no compiler to enforce it". Actually, maybe a compiler isn't the key word, but automated checks is the idea. A type-checking compiler is just one solution, albeit a pretty powerful one. Type inference makes it much less onerous these days. But even staunch dynamic language devs have come around to embrace the use of linters. So while it's often seemed that static and dynamic language users would always be at odds, perhaps a convergence is slowly happening.
The sounds like a limitation of promisify, not inherently of callbacks. There are many situations and styles where callbacks are perfectly adequate, and promises offer no benefit, so referring to it as the "old callback style" may be a bit premature.
Promises are superior in general, I guess that has been discussed already[1], many times. Promises may indeed offer just an insignificant benefit sometimes (you can even call it just a different style), for example when you are first writing a monolithic prototype. In this worst case, you would simply make a giant chain of ".then" methods, any your gain is you can get creative with the error handling if you want. However, the greatest benefit revels itself later: The composability that comes with them will help you immensely when refactoring.
[1]: One example: http://softwareengineering.stackexchange.com/a/302456/9451 Another: http://blog.parse.com/learn/engineering/whats-so-great-about...
Performance may be a tradeoff for the current engines, but I'm optimistic that it will be optimized away as it doesn't add too much semantics over the callbacks. Maybe error handling is tricky.
I would say they are superior to callbacks as object-oriented code is to procedural code - that also has some performance cost in many engines.
But, you are saying tradeoffs - plural. I, as an experienced web developer, am sincerely curious what any other can be?
That is not a universally-accepted statement either.
I'm not interested in taking sides here, only pointing out that there are a wider variety of extant opinions than you seem to realize.
You're just saying that if you make a mistake and use it wrong it doesn't work. That applies to all code, not just promisify.
> Also, the library writers are not writing the code with any guarantees that promisify will always work. So even if it works now, it could break in the future.
That also applies to all code, not just promisify. If the author changes their API without a major version bump, that can break any code that relied on the old API.
Promisify is deterministic and works as specified, 100% of the time. If it doesn't work, that means you passed a function with the wrong signature.
To my understanding, the following code examples do the same thing:
readFilePromise("file").then(... txt ...);
txt = readFileSync("file");
txt = await readFilePromise("file");
Why not use the one in the middle ?In a simple script you run from your shell, the middle one is fine.
I think it's perfectly fine to wrap standard nodeJS modules into your own module, Promisify or what not. Almost all NodeJS modules does actually use standard modules in some way, it would be very naive to suggest that the standard library should start returning Promises, as it would break almost every NodeJS module ever created.
I also think that Promises only makes sense when you only expect one return, it would note make sense to use Promises in NodeJS streams.
With await, you know. If there is no await, the call will not block, and more importantly, shared state cannot be modified from outside of that block of code. If there is await, there are no guarantees and you need to take the necessary precautions.
Use $q.when() in your code if you aren't sure you're getting a promise.
The whole promisify is a good example of general node.js culture. It's considered "good enough" and everyone just uses it. However, anyone who has done software development in more solid languages would feel very uneasy using something like this. It's not really the fault of the JS developers, the dynamic nature of the language offers no other choice. For example, when Java added lambdas, since it has static typing it could consider a certain type of class (a single method class) as automatically a lambda. This allowed full backwards compatibility with any old library that conformed to this (and many libraries such as Guava did use single method class as a kind of "ugly" lambda).
node.js has a convention for callbacks (function(err, result)), but unfortunately it's only a convention and there's no compiler to enforce it. So automatic promisification is not possible. That leads to the current situation. There's all these new language features in JS, but everyone is still sticking to the "lowest common denominator" of callbacks. There's no path forward for existing libraries. The only way is a complete rewrite of libraries using the new Promise/async/await style.