Zalgo is Bad News.
This is in fact about conventions for using callbacks in Javascript.
When you call a function in Javascript that takes a callback argument, you should know whether that callback will be called synchronously or asynchronously, i.e. on the next (or some future) event loop tick.
performAction(result => { console.log("received", result) })
console.log("after action");
The following code contains a way to unleash Zalgo:
const xhr = nanoajax.ajax(nanoOptions, (code, responseText) => {
const contentType = xhr.getResponseHeader("Content-Type"); // ZALGO
callback(code, contentType, responseText);
});
What exactly happens if the result callback is invoked synchronously?
Javascript variable definitions are block-scoped, so referring to xhr
before it is initialised is permitted, event though it's a const
If the callback may be deferred, always defer it. Maybe wrap it in a function
that simply does a setTimeout(callback,0)
There is a dezalgo package for this.
In fact, nanoajax
provides a solution by passing the XHR as a third parameter to the callback:
const xhr = nanoajax.ajax(nanoOptions, (code, responseText, xhr) => {
const contentType = xhr.getResponseHeader("Content-Type");
callback(code, contentType, responseText);
});
This shadows the xhr
declaration, so it has its own ugliness, but does work.
Use Promises: they will always defer execution of subsequent steps
const promise = new Promise(resolve => {
resolve("xyzzy");
}).then(x => console.log("result", x));
console.log("promise", promise);
// Prints:
// "promise" ...
// "result"
But note that the executor is invoked before the Promise constructor returns!
This creates a fun alternative way to defer execution:
Promise.resolve(true).then(() => {
console.log("deferred");
});
This also explains why Sinon's clock fakery doesn't work with code that uses Promises, JSYK
zen-observable goes to some effort to avoid unleashing Zalgo:
const Observable = require("zen-observable");
const subscription = new Observable(observer => {
observer.next("abcde");
observer.next("xyzzy");
observer.complete();
}).subscribe(value => {
console.log("received", value);
subscription.unsubscribe();
});
// prints only "abcde"