18:38 | <ptomato> | I'm looking into the Array.fromAsync proposal and have a question about what people's understanding of "Zalgo" is (to explain the jargon, Zalgo is a reference to a meme, that is used as shorthand for the situation where an async operation sometimes calls a callback and sometimes doesn't) |
18:39 | <ptomato> | is throwing an error immediately, on bad input, Zalgo? |
18:40 | <ptomato> | i.e.
|
18:41 | <ptomato> | the Array.fromAsync proposal says yes and takes care to check its input only inside its async closure, which means rejecting the promise, not throwing an error synchronously |
18:41 | <ptomato> | Atomics.waitAsync, on the other hand, does throw errors synchronously on bad input: (steps 1 and 2 of https://tc39.es/proposal-atomics-wait-async/#sec-dowait) |
18:43 | <ptomato> | reading the original definitions of Zalgo, I'd say this is not Zalgo as originally defined |
18:44 | <ptomato> | however, maybe it is still desirable to do one or the other; in which case we should probably adjust one of these two proposals |
18:45 | <Kris Kowal> | Zalgo in short is a combinatoric explosion of behaviors that may be either synchronous or anachronous, such that it is not practical to test every combination that will be seen in production. |
18:46 | <Kris Kowal> | Vetting inputs is I believe debatable. It depends on whether the errant behavior can possibly vary at runtime. |
18:47 | <Kris Kowal> | Being JavaScript, it almost certainly can. |
18:47 | <ljharb> | the language has explicit decided with async function that sync exceptions should never be thrown from something that returns a promise. there's a few exceptions, i think, if you're calling something on the wrong receiver, but generally we should avoid that |
18:47 | <bakkot> | ptomato: I would say that Atomics.waitAsync is actually a sync function, despite the name |
18:47 | <bakkot> | it does not return a promise; it returns an object which is synchronously usable |
18:47 | <ljharb> | can it return a promise? |
18:47 | <ljharb> | ah ok |
18:47 | <ljharb> | then i would agree |
18:47 | <bakkot> | (one of the properties of that object can be a promise) |
18:47 | <shu> | Atomics.waitAsync is the way it is explicitly to avoid zalgo |
18:47 | <ljharb> | zalgo is basically "always, or never, return a promise" |
18:48 | <ljharb> | "an object containing a promise" is definitely not a promise :-) |
18:48 | <shu> | in particular, Atomics.waitAsync needs to fail fast because it's built to mutexes -- you can't wait until the next microtask tick to find out you couldn't acquire the lock and need to go into the slow path |
18:49 | <Kris Kowal> | the language has explicit decided with |
18:49 | <shu> | so, instead of always returning a Promise it does this wrapping |
18:49 | <ptomato> | fair enough. I got misled by the first sentence of MDN on Atomics.waitAsync: "The static Atomics.waitAsync() method waits asynchronously on a shared memory location and returns a Promise." |
18:49 | <shu> | it waits asynchronously if it waits at all |
18:49 | <ptomato> | reading further down, you are right that it actually does not return a Promise |
18:49 | <shu> | if it doesn't wait, you can find out synchronously |
18:52 | <ptomato> | the language has explicit decided with |
18:52 | <shu> | all that said i love zalgo |
18:52 | <shu> | i wish we had zalgo |
18:52 | <shu> | but alas |
19:02 | <ptomato> | well, in lieu of spec conventions, if anyone has a pointer to context about that decision, I'd be happy to read more. maybe I'll write a blog post on this or something |
19:03 | <joepie91 ๐ณ๏ธโ๐> | ptomato: https://blog.izs.me/2013/08/designing-apis-for-asynchrony/ is pretty much the canonical source on this topic |
19:03 | <ptomato> | thanks, but I linked that document above already ๐ |
19:04 | <joepie91 ๐ณ๏ธโ๐> | oh, sorry. |
19:04 | <joepie91 ๐ณ๏ธโ๐> | only half paying attention today |
19:04 | <ptomato> | sync exceptions are not Zalgo according to that definition, so that's why I'm interested in the context of where we decided to depart from that |
19:05 | <joepie91 ๐ณ๏ธโ๐> | I'm unsure of the exact process behind that decision, but it does meet the principles behind zalgo; errors that are sometimes synchronous and sometimes asynchronous make it difficult to reason about the behaviour of a function |
19:09 | <ptomato> | I tend to agree, although the article (and the one from Havoc Pennington before it) concentrates, rightly IMO, on the situation where a callback is called sometimes synchronously and sometimes asynchronously, which is much, much worse |
19:10 | <Ashley Claymore> | I found it more of an issue when .then was more common. Would frequently see code like .catch(handleError) but sometimes the code would sync throw and that wasnโt handled.Now await is here itโs less common, and code uses a catch block for both paths. But still seems like a good principle to follow |
19:11 | <ptomato> | it's effectively a slightly different question; is it OK for the callback to not be called at all? (and I think there is good reason to answer no) |
19:13 | <ptomato> | which is apparently also what TC39 has concluded as well |
19:17 | <Ashley Claymore> | Many people, myself included, use zalgo also for when an api always calls the callback, but can either get the call before the outer call returns (current tick) or after (fresh tick) |
19:19 | <Ashley Claymore> | I think it is ok to further expand that to: when will the error information be available |
19:19 | <Ashley Claymore> | cuts down code paths if always async |
19:20 | <ptomato> | if the call happens before the outer call returns, isn't that effectively calling it synchronously? |
19:25 | <Ashley Claymore> | yeah |
19:28 | <Ashley Claymore> | ah right, and a sync error would be โnot calling the callback at allโ. Which yes is annoying. If the API returns a Result like container, then it has somewhere to return errors. It something is sync returning a value directly, it kinda has to throw if it wants to โreturnโ an error |