12 ms·
The JavaScript language has improved a lot since node.js was released (Promises and soon async/await). However, at this point NPM has a lot of libraries writte
by bit_logic 10y ago
The JavaScript language has improved a lot since node.js was released (Promises and soon async/await). However, at this point NPM has a lot of libraries written in the old callback style and it seems even new libraries are still doing this. There are libraries like bluebird and Q which can "promisify" a callback style library, but I think using these is a bad practice. It's something that works most of the time, but sometimes breaks when the callback code isn't what the promisify function expected. 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.
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.
- mpfundstein 10y agoWhile - in general - you are quite right, most of the bigger libraries that I use in production offer interfaces for both styles. Mongoose for instance. You can easily use it with Promises or with the old-school callback style. Same for unit test libraries. And this trend will only continue. There is a lot of cool research being done in combining JS with hardcore FP (fantasy-land, ramda) and this will of course influence future developments. Not to forget the FRP community around React/Bacon etc... I predict that in a year or two, the majority of devs will use Promises as if they always have been there.... And hey. Promises are Monads [1], so maybe we will also soon see a lot of Eithers and Maybes in Production code. I taught today a lecture about this at my company, and people with no prior exposure to FP immediately loved it. 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.
- mayank 10y ago> It's something that works most of the time, but sometimes breaks when the callback code isn't what the promisify function expected. 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.
- egeozcan 10y ago> There are many situations and styles where callbacks are perfectly adequate, and promises offer no benefit 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 http://softwareengineering.stackexchange.com/a/302456/9451 Another: http://blog.parse.com/learn/engineering/whats-so-great-about-javascript-promises/ http://blog.parse.com/learn/engineering/whats-so-great-about...
- jborica 10y agoPromises are not superior in general. There is a significant performance hit that, in some problem areas, absolutely should not be ignored. Neither of those links claims that promises are superior in general. They provide additional, valuable features, but with tradeoffs.
- egeozcan 10y agoWell, I stand corrected. Yes, there definitely is a performance penalty for promises. 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?
- kemitchell 10y agoI write and prefer libraries written in error-first callback, continuation-passing style. I also prefer `require()` to new `import`. Those choices have rather little to do with backwards compatibility, from my point of view. I think they are better choices, which just so happened to be the norm before ECMAScript began to change again. Opinions differ, and will differ, forever and ever, amen. 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.
- kpil 10y ago[There are] standards-compliant ECMAScript runtimes everywhere and we all dream that it had been scheme instead. Fixed that for you.
- deleted 10y ago[deleted]
- kemitchell 10y agoI'd be happier with a Scheme-ier JavaScript, and I'm not alone, but that's not everybody.
- stiGGG 10y ago
- kevingoslar 10y agoThere is a way to support both styles at the same time, and most libraries (that I'm aware of) use it. If a callback is given, call it with the result. If not, return a promise for the result. This doesn't require a rewrite of existing libraries, but merely a new minor version. 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.
- acjohnson55 10y agoI have to agree. It's the wild west. You just have to be prepared for things to break, and if you're lucky, they break early and reliably enough that you don't have Heisenbugs. I'd say it's a bit of dynamic language culture, overall. Although, perhaps Javascript sees a particular flavor of it, with its first-class, always-variadic functions. 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.
- krisdol 10y agoCallbacks and events are significantly faster than promises, and promises I think most see as a wrapper around callbacks, with convenience functions for running and waiting on multiple concurrent async functions. 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.
- nicoburns 10y agoI actually think using your promise library's promisify methods are best practice. Reason: there are multiple promise implementations with different methods available (e.g. the Bluebird library extends the promise/A standard significantly), and thus if libraries included promises, a conversion from whichever promise library the library was using to the promise library which you are using would be necessary, and this would be considerably less efficient.
- spion 10y agoI have to disagree with you regarding promisify. I think its part of the solution, not the problem - it forces libraries to actually adhere to conventions, otherwise they are not promisifyable. 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.
- exclusiv 10y ago> sometimes breaks when the callback code isn't what the promisify function expected Use $q.when() in your code if you aren't sure you're getting a promise.
- z3t4 10y agothe problem with promises is that they spread like a virus. And there is no cure. So i want to have a choise not to be infected.
- ilaksh 10y agoYou don't know what you are talking about. Not _everyone_ is sticking to callbacks. Many (if not most) are now on promises or async/await. 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.
- callumlocke 10y ago> [promisification is] something that works most of the time, but sometimes breaks when the callback code isn't what the promisify function expected 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.
- z3t4 10y agoPromises often end up "infecting" every async function. Promises are easy to add, but hard to remove. 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 ?
- drewgross 10y agoBecause it blocks anything else from happening. If on a server, it will prevent that process from handling any requests until the file has been read, and in a browser-like environment it will prevent the UI from updating and will prevent all UI interaction until the file has been read. In a simple script you run from your shell, the middle one is fine.
- z3t4 10y agoThank you. I can see the advantage of Promises and async/await. But most of the time, I want to read the file right now, or define what should happen after it has been read. I can not imagine any circumstance when I maybe want to read a file later witch I think is the only advantage of Promises over callbacks, EventEmitter and Sync. 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.
- spion 10y agoBecause it makes points of potential interleaving obvious. Without await, you have no idea whether the call will block or not, and whether whatever shared state you accessed before the call will remain the same after. 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.