Skip to content

Instantly share code, notes, and snippets.

@bluejava
Last active December 22, 2022 06:27
Show Gist options
  • Save bluejava/9b9542d1da2a164d0456 to your computer and use it in GitHub Desktop.
Save bluejava/9b9542d1da2a164d0456 to your computer and use it in GitHub Desktop.
A Very Fast Javascript thread yield (see blog posting)
// See http://www.bluejava.com/4NS/Speed-up-your-Websites-with-a-Faster-setTimeout-using-soon
// This is a very fast "asynchronous" flow control - i.e. it yields the thread and executes later,
// but not much later. It is far faster and lighter than using setTimeout(fn,0) for yielding threads.
// Its also faster than other setImmediate shims, as it uses Mutation Observer and "mainlines" successive
// calls internally.
// WARNING: This does not yield to the browser UI loop, so by using this repeatedly
// you can starve the UI and be unresponsive to the user.
// Note: For an even faster version, see https://gist.github.com/bluejava/b3eb39911da03a740727
var soon = (function() {
var fq = []; // function queue;
function callQueue()
{
while(fq.length) // this approach allows new yields to pile on during the execution of these
{
var fe = fq[0];
fe.f.apply(fe.m,fe.a) // call our fn with the args and preserve context
fq.shift(); // remove element just processed... do this after processing so we don't go 0 and trigger soon again
}
}
// run the callQueue function asyncrhonously, as fast as possible
var cqYield = (function() {
// This is the fastest way browsers have to yield processing
if(typeof MutationObserver !== "undefined")
{
// first, create a div not attached to DOM to "observe"
var dd = document.createElement("div");
var mo = new MutationObserver(callQueue);
mo.observe(dd, { attributes: true });
return function(fn) { dd.setAttribute("a",0); } // trigger callback to
}
// if No MutationObserver - this is the next best thing - handles Node and MSIE
if(typeof setImmediate !== "undefined")
return function() { setImmediate(callQueue) }
// final fallback - shouldn't be used for much except very old browsers
return function() { setTimeout(callQueue,0) }
})();
// this is the function that will be assigned to soon
// it takes the function to call and examines all arguments
return function(fn) {
// push the function and any remaining arguments along with context
fq.push({f:fn,a:[].slice.apply(arguments).splice(1),m:this});
if(fq.length == 1) // upon adding our first entry, kick off the callback
cqYield();
};
})();
@AaronConlon
Copy link

AaronConlon commented May 18, 2021

It's cool.But why not use "process.nextTick" before the "setImmediate"? @bluejava

@bluejava
Copy link
Author

Thanks - I think there are some speed improvements in the version included in Zousan. I would recommend including Zousan and calling Zousan.soon rather than pasting this gist (though you are welcome to of course!) Zousan is tiny and maintained. I've been using it in production for many years - and Zouson.soon for a high performance centralized message bus used heavily in production for a few years as well. If I get a chance I will publish the message bus as well.

Zousan: https://github.com/bluejava/zousan

@AaronConlon
Copy link

Happy to see your comment.😁
I am learning how to create a simple Promise implement,and glad to see your articles on your blog.
Let's keep going.

@gabrielpetersson
Copy link

gabrielpetersson commented Dec 21, 2022

MutationObserver is queuing a microtask, as per defined here https://dom.spec.whatwg.org/#mutation-observers
So I think this can be done even faster by just directly using the window.queueMicroTask? @bluejava lmk if you try this out!

The event loop is defined as such if you are interested and shows why the above works well, awesome read! https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

@bluejava
Copy link
Author

Thanks for this Gabriel - will definitely give it a try!

The effect on performance will likely be extremely limited, since the scheduling of the microtask happens only once per event loop iteration - so if you fire off 1000 soon() calls, only the first one utilizes the MutationObserver to schedule a microtask and the rest pile on..

But it's a much more elegant solution (and Node friendly) so I like it! Thanks for pointing it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment