is summoned
The Zalgo post does need to be updated, it was obviously written back when Promises were not yet a well defined language feature.
I haven't read the proposal here in detail. Please excuse anything that might have already been covered, as I don't have an informed opinion about the language feature being discussed, but I would like to clarify what is being attributed to me and that post.
The fundamental issue that "Zalgo" refers to (and that Havoc's blog post on callbacks discussed in less silly terms) is this:
let called = false
const cb = () => { called = true }
const doSomething = (cb) => {
// does some things, eventually calls cb()
}
doSomething(cb)
// EXACTLY ONE of these must be true, always:
// assert(called)
// assert(!called)
Zalgo is so named because of the maddening experience of debugging timing issues that occur when it is impossible to know the order of operations from reading code or inspecting return values.
This has been extended (I'd even say, stretched) to imply that I'm saying that all calls to all functions must deterministically and consistently return either a promise or a value and not both. And, certainly, there are problems that can result from such ambiguity if it is not managed. However, this is not Zalgo-releasing, because it is clear what order the methods will have been called in:
let called = false
const cb = () => { called = true }
const doSomething = (cb) => {
if (Math.random() < 0.5) {
// gotta defer for some reason
return Promise.resolve().then(() => cb())
} else {
cb()
}
}
await doSomething(cb)
// this assertion always passes, no maddening timing challenges, no Zalgo
assert(called)
That is, the language now contains a straightforward and idiomatic way to consistently and deterministically indicate that an action could not be performed synchronously and had to be deferred, and to easily wait for that action to be completed before moving on in that case.
As mentioned in the post, this sort of "return an indication that it could not be performed synchronously, and a means to know when to continue" is a more advanced/complicated API for the user, with more checks and things to get wrong, and so should not be used unless (a) asynchrony is required less often than not, and (b) the performance benefits of avoiding asynchrony is great enough to justify the complication.
The weighing of these trade-offs is as much art as science, of course, and I'd argue that the existence of async/await makes the cost much lower.
Just in briefly scanning the discussion here, I don't see any mention of this, but for completeness's sake, in the Zalgo post also I failed to mention the concept of a "Zalgo-preserving API", which has also become a term of art. This is another case where it's perfectly acceptable for a utility or iterator function to be maybe-sync, based on the function passed in. So, despite being technically nondeterministic and inconsistent, it's consistent to what the user passes in.
const preserveZalgoForEach = (list, cb, i = 0) => {
for (; i < list.length; i++) {
if (!(i in list)) continue // skip holes
const res = cb(list[i], i)
if (res instanceof Promise) return res.then(() => {
preserveZalgoForEach(list, cb, i + 1)
})
}
}
(While there are of course complicated ways to accomplish the same thing only using functions instead of Promises, the existence of a first-class language feature to indicate "thing will be done later" makes it much more straightforward.)
I bring this up only because there have been cases (for example, throughout the Web Streams API) where I worry that an overly cautious approach to Zalgo issues has resulted in interfaces that are unnecessarily slowed down due to contracts enforcing Promise deferral. Rather than reducing timing complications, these can even sometimes work to make those issues more challenging, as the Node team has been finding for years, because synchrony is so fundamental and so easily exposed in the API surface.
So if the suggestion here is "Make a thing that can usually be synchronous, instead be always asynchronous", then that might be justifiable based on the specific trade-offs, but please at least don't claim that I told you that you need do that. There are very often ways to achieve a better result, and it's worth making the case on the merits of each option available for this specific situation.