img.src = placeholderFor(viewport);
loadRealImage().then(src => img.src = src);
// carry on img.src = placeholderFor(viewport);
loadRealImage().then(src => img.src = src);
// carry on function loadImage(src) {
loadRealImage(src).then(img => /*... return img from loadImage ...?*/);
}
"Just make it an async function." Well yes, but then the async nature contaminates the rest of your code. You have to make sure to set up a toplevel try-catch for every one of your async chains, for example.What do you do when you want to have an async generator? That is, you want to both await on something, and yield N values from the function. JS makes that difficult.
There are all kinds of limitations like this, and everyone has their own favorite hacks around them. But they're hacks, not unification. And yes, hacks can be effective, but a unified framework can be tactical.
const { all, the, values, you, want } = await multiValuedReturn();
?
I think the problem sillysaurus3 is facing is that he wants complete convenience of the kind that async/await bring, meaning being able to execute code regardless if it finishes now or later, but always pretending that it finishes now. That's just not possible, and for good reason: some things happen now, and some things happen later, and if you need ultra-fine-grained control over what happens when, then you need to be very explicit about what's happening and how and when. It's messy and ugly, and we're working towards cleaning that up, with async/await being a great step forward, but this is inherent to the concepts of sync/async control flow, and the assembly solution is much more of a hack to solve this (if I even understand the solution correctly) than Promise/async/await/etc.
Green threads. No silver bullet, but sometimes you upgrade your pistol.
JS needs greenthreads. There's no reason you shouldn't just spin up a thread for every blocking context. This is arguably what async/await already does, but you don't have control over the toplevel loop. Point out where your while (userIsOnWebsite) { ... } loop is. :)
Having a toplevel loop is very important for simplicity -- but even moreso for what you mention: having ultra fine-grained control, and not constantly fighting with the underlying scheduler / ecosystem.
This is off-topic from the current JavaScript discussion, but even though Python introduced "first-class" async support a while ago, I still exclusively use gevent [1] for all of my concurrency needs. It provides full greenthread support for Python. It's extremely performant on top of being simple and clean to integrate. In contrast, Python's asyncio and async/await feels needlessly verbose and tortuous, often with full rewrites or replacements of libraries required to actually take advantage of the features (e.g. https://github.com/requests/requests vs. https://github.com/aio-libs/aiohttp). With gevent, I can just put whatever code I want into a greenthread and get instant asynchronicity (excluding hiccups with a few 3rd party libraries that have network I/O in native extensions, which is rare).
The one downside is it achieves its magic with standard library monkeypatching, which is pretty hideous, but it's done very seamlessly. Developers are able to write the exact same code with or without monkeypatching and not worry about what it's doing. I've never encountered an issue with the monkeypatching when using any standard or 3rd party library.
I ended up tracking down which goroutines were likely to spawn kernel threads (gross global reasoning), and applied a rate-limiter to that set.
Most people, including myself, use gevent for I/O bound applications (where it excels), so this usually doesn't pose any issues. For CPU bound tasks where performance is important, I'd probably use something other than Python (likely Go, personally).
longjmp, however, is a crucial missing facility in JS. Emacs relies on it heavily in its design -- it's how catch / throw work, and it's why you can do things like
(catch 'foo
(map (fn (x)
(if (= x 42) (throw 'foo x)))
values))
Without longjmp, you can't do that. Just as you can't in JS.So what? Well, that means you can't write emacs, because you're limited to what JS provides you. An entire class of software is beyond your ability to write, because you cannot provide the same features that other runtimes give.
This gets me started on the lack of any kind of reasonable error definitions in JS. In elisp, you define errors. Imagine you want to write some code that parses some parens -- it turns "(a b (c))" into ["a", "b", ["c"]]. What do you do when your program encounters "(a b" and then the end of the string? Throw a scan error!
Not in JS. It's considered poor manners to throw errors to the people using your library. Worse, it's a pain in the ass for users to catch and respond to errors. If you use someone's library, you usually don't expect to have to wrap it in a try-catch. And the code is massive:
try { operation } catch (e) { if (e instanceof ScanError) { do something else } }
Contrast that with elisp:
(condition-case nil
operation
(scan-error do something else))
There's no contest. It's way easier to write the latter than to use the tools JS gives you. But it's a cultural difference, and culture is slow to change.That's why webassembly at least gives an escape hatch.
Well JS does provide this via exceptions.
Second it's totally crazy that emacs depends on longjmp, which is an insane decades-old wart that ought to just quietly die.
Third...does web assembly even support longjmp? I'm pretty sure it does not.
> That's why webassembly at least gives an escape hatch.
Escape hatch from...writing five lines instead of three?
function foo() {
return doSomething();
}
async function doSomething() {
...
throw ScanError();
}
Spot the bug? That's an async function. Every JS programmer worth their salt will tell you how many times they've been annoyed to discover a missing await, and that the promise is failing mostly-silently (or worse, it works by accident until it doesn't, since that code will work fine most of the time).Well JS does provide this via exceptions.
Second it's totally crazy that emacs depends on longjmp
It's not crazy.
function bar() {
[1,2,3].forEach(x => {
if (x == 2) /* return from bar...? */
}
}
Why can't you write this code? I mean you "can": function bar() {
let tag = [];
try {
[1,2,3].forEach(x => {
if (x == 2) { tag.value = x; throw tag; }
}
} catch (e) {
if (e === tag) {
return e.value;
}
}
But holy crap that's terrible. EDIT: I also forgot to rethrow the error, showing just how easy it is to screw up.Just write it as a for loop, then. Well sure, except for the hundreds of libraries that don't support that style. What do you do when you want to return prematurely from the iterator functions you pass to them? Now you can't just make a for loop unless they provide Symbol.iterator. And 9 times out of 10 they give you a promise, meaning you're forced to convert your code to async style.
It's a huge mess, and longjmp saves you a lot of headaches in disciplined situations. Every tool has its place, and it's strange to argue that a computer should be able to do less, not more.
The goal is to save you time in the long run. And those 5 lines better be exactly right, or you'll waste a lot.
"can't write some class of software" is just wrong, is all