6 ms·
> It's just that noone has been able to replicate that kind of success [of Rust's and Go's achievements] in the web-based frontend space. While it's easy to do
by hombre_fatal 3y ago
> It's just that noone has been able to replicate that kind of success [of Rust's and Go's achievements] in the web-based frontend space.
While it's easy to dog on Javascript, it's also necessary to consider what Javascript does right.
The main thing that comes to mind is JS' async-everything, async-by-default, and first class async abstractions (like the Promise). Not necessarily something you want all the time, but certainly a powerful feature when it comes to IO-bound tasks and UI development. We don't give enough credit to JS for this imo since we take it for granted.
But consider something like this (JS) using WaitGroups in Go:
const aPromise = promise()
const [b, c] = await Promise.all([promise(), promise()])
const a = await aPromise
- lhnz 3y agoNot going to comment about Go here, but the JavaScript code above is confusing for a lot of people. Promises begin to resolve immediately on creation whether or not we've attempted to unwrap their value using `await`. Therefore, it's quite possible that the first call to `promise()` might resolve after 3 seconds, while the next two calls could take 7 seconds and 10 seconds respectively. In this case, the `Promise.all` would produce a result after 10 seconds, and then the next line `const a = await aPromise` would unwrap the value within `aPromise` immediately, since it would already have been fulfilled.
- lhnz 3y agoNot going to comment about Go here but JavaScript code like what is written above confuses many people. `Promise`s begin to resolve immediately on creation whether or not we've attempted to unwrap their value using `await`. Therefore, it's quite possible that the first call to `promise()` might resolve after 3 seconds, while the next two calls could take 7 seconds and 10 seconds respectively. In this case, the `Promise.all` would produce a result after 10 seconds, and then the next line `const a = await aPromise` would unwrap the value within `aPromise` immediately as it would already have been fulfilled 3 seconds into the `await Promise.all([promise(), promise()])`.
- lhnz 3y agoNot going to comment about Go here but JavaScript code like what is written above confuses many people. `Promise`s begin to resolve immediately on creation whether or not we've attempted to unwrap their value using `await`. Therefore, it's quite possible that the first call to `promise()` might resolve after 3 seconds, while the next two calls could take 7 seconds and 10 seconds respectively. In this case, the `Promise.all` would produce a result after 10 seconds, and then the next line `const a = await aPromise` would unwrap the value within `aPromise` immediately as it would already have been fulfilled 3 seconds into execution of the `await Promise.all([promise(), promise()])` line.
- taeric 3y agoWhat JavaScript does right, is to have an installed VM on basically every computer out there. If you are building a consumer focused thing, it is hard to argue for any other installation method. Especially when you consider you can remove the need to track multiple deployments in the same way, since you can basically force an update to all users.
- ttfkam 3y agoMicrosoft had VBScript installed on >90% of all browsers at one time. Google almost installed Dart by default in Chrome and then walked it back. Java was installed on all browsers for a decade, but no one wanted it. WebAssembly allows for languages like Rust to be used, but its adoption is still very niche. JavaScript lives on. It's not just inertia. IE for example supported multiple languages at the same time. As a lingua franca, JS is simply better than the alternatives. Python is subjectively cleaner than JS, but it isn't sufficiently better to warrant replacing JS, especially after JS started becoming more Pythonic. Lua is arguably a step backwards. Go adds a necessary compile step. Folks already gave Java a shot. C/C++ was a hard-learned lesson after ActiveX regarding web security. Perl ain't gonna be it obviously. Rust has a much too steep learning curve for the vast majority of web developers to tolerate. Ruby is slower and also not sufficiently better. I know folks don't like to hear it, but JavaScript is nowhere near as bad as folks like to go on about it. In fact it's so flexible, an entire ecosystem rose up around it on the server side more than a decade after its client-side debut. If JS were really that bad, no one would adopt it for other areas if they didn't have to. It is familiar and gets the job done. We only highlight its shortcomings because we've had almost 30 years to pick it apart and dissect it. Google could team up with Apple and make Swift a supported language. Within 2 years, it would be on >90%+ of all devices. Maybe 95%. And it wouldn't matter. Sure, a bunch of folks would use it, especially if they were Apple devs. But the vast majority would ask, "What would this new language give me that JS can't do? Is it actually worth rewriting apps and retraining my staff?" Honestly, the answer is 'no'. Because JS really isn't bad. It has warts (though many/most of them due to the DOM rather than the language). It has legacy. But after 30 years, it's still doing surprisingly well at both speed, flexibility, and the ability to evolve.
- 3y ago
- lhnz 3y agoI agree with you that JavaScript's async abstractions are nice, but I think a lot of people will misunderstand the example you've given, as ensuring that the asynchronous value for `b` and `c` will be resolved prior to the asynchronous value of `a`. Promises begin to resolve as soon as they are created. Therefore, assuming that the three executions of `promise()` in that code take 3 seconds, 10 seconds and 2 seconds respectively, we should expect `aPromise` to be resolved during `Promise.all([promise(), promise()])` immediately prior to `b` and `c`. The only thing is that we do not unwrap `aPromise` with `await` and point `a` towards it until 10 seconds after `await Promise.all([promise(), promise()])` was called.
- arp242 3y agoI think async-by-default was an interesting idea and well worth trying, but I don't really think it was a good idea. Turns out that most things you want to do are synchronous, and "sync-by-default unless mentioned otherwise" makes a lot more sense. This includes I/O by the way, because most of the time you want to wait until an operation has finished. Or to put it in another way: JavaScript makes the uncommon case easy and the common case hard(er).
- bilalq 3y agoI disagree. I'd take JS-style promises over trying to manage Futures in ForkJoinPools or thread pools any day. Being able to write async expressions in parallel by default means even junior devs take advantage of parallelism. I've seen plenty of code written in Java and Ruby where multiple network and DB requests are made in serial despite having no dependency on each other. The usual reason is that there's just a lot more friction to have it be parallel there.
- arp242 3y agoI have also found Promises very hard to reason about at times: "okay, so I have some code here, and when exactly is this run?" can be a difficult question to answer with Promises. Part of that is inherent in async code, but part of that is also because IMHO Promises make it harder than it needs to be. I never really used Java, but I have used Ruby and Python (IIRC Python's APIs were modelled on the Java ones) and I agree it can be painful. The thing is, even with an awkward async implementation it's something you have to deal with relatively infrequently when synchronous is the default (as it is in most languages). When you do it can be a pain, but I'd rather have this "occasional pain" vs. "pain every time I want to do any I/O operation". Personally I like how Go does things.
- peferron 3y ago> I have some code here, and when exactly is this run? If there are `await` keywords previously in the function, then the line you're looking at will run after these async calls are done. Otherwise it'll run ASAP. Is there something else to it?
- bamfly 3y agoAsync in interface by default was a mistake and led to a ton of pain. Actually-async-under-the-hood is fine. See: how much JS is rightly and justifiably littered with "await" on seemingly almost every line, now that that's an option. It's downright comical to look at, and as clear a sign of mis-design in a language/runtime as you can get. Nine times out of ten (maybe more...) you just need to treat all that async stuff as synchronous, from the perspective of the current block of code. "Await" or something like it should have been the default behavior of async calls. A great deal of the mess in Javascript over the last couple decades has been due to going the other direction with that default.
- ttfkam 3y ago> It's downright comical to look at, and as clear a sign of mis-design in a language/runtime as you can get. That's your opinion. Some of us prefer to know when a call is I/O-constrained and when the execution queue is being interrupted. JS had fearless concurrency before it was cool. When was the last time you heard of a JS program in a thread deadlock under load (other than a VM bug)? Never. It can get caught up in an infinite loop like any language, but that's not deadlocking. Because the language doesn't allow it. Not "makes it easier to avoid". Straight up doesn't allow it. That's no small thing, and it's not something Go can claim.
- bamfly 3y ago> Straight up doesn't allow it. Right—so making the most-commonly-desired behavior (await-like behavior) default would have been a pretty big win, then, and not harmed that quality at all. Meanwhile it's only with the addition (and liberal sprinkling across most active JS codebases) of async/await that the default behavior doesn't result in ugly, awful code—wrestling with that defect is basically the story of the evolution of JS during and since the '00s. The number of person-hours lost to unwanted async-by-default semantics, writing competing libraries to work around it and make it less painful, learning those libraries, contorting code to use them just so you can (more often than not) cancel out the (perceived) effects of async behavior, learning the next set of libraries after that set's defunct, et c., must have been enormous—so incredibly large that I don't think there's any saving that decision from the "mistake" pile. Now, it's merely funny and a little unsightly, seeing "await" rivaling, on a usage-count basis, keywords like "const" in so many JS codebases.
- jerf 3y ago"But consider something like this (JS) using WaitGroups in Go:" Sure, though if this is something you're doing a lot of it's not hard to abstract out. However, let me put on the other side of the balance the sheer staggering quantity of code there is out there that just "await"s everything in line as if they were writing some version of Go that required you to specify every function that could potentially be async, because Promise.all and friends are quite uncommon. Before you jump up to protest to the contrary, go run a grep over your code base and count the "await"s that are simply awaiting the next thing in sequence versus doing something less trivial. Even those of you who write this code fluently will probably discover you have a lot of things just getting directly awaited. The exceptions, being rare, are cognitively available; the number of times you almost unthinkingly type "await" don't register because they are so common. Only a few of you doing something fairly unusual will be able to say with a straight face that you are using Promise.whatever abstractions all the time. Even fewer of you will be able to say you use all different kinds and construct your own and couldn't solve the lack of some particular abstraction in Go in just a few minutes of writing some function.
- ttfkam 3y agoPromise.all, Promise.race, and Promise.allSettled are a non-trivial amount of my await calls. Also, while you may consider "await" noise, I consider it signal. I want to know when the execution queue has a break 100% of the time. Implicit await would make ordinary JS code a nightmare to debug. Contrast this with Go where you must synchronize shared resources. Yes, the go-routine model allows relatively simple concurrency. However concurrency management is simply not a concern in JS-land. Yes, JS can be optimized so that more happens in parallel, but deadlocks can happen. Multiple writers to the same object can't happen. Passing data between threads enforces ownership and visibility out of the box. JS is bulletproof from a developer standpoint, which is a boon to security and absolutely, positively required in an environment where you're executing random code from random authors on the internet. JavaScript really doesn't get enough credit for what it's accomplished.
- scotty79 3y agoPersonally I feel like having functions and closures as first class objects is the killer feature. Without that, even dynamic languages feels unnecessarily restrictive when you tried JS.