Skip to content

Instantly share code, notes, and snippets.

@bluejava
Last active December 22, 2022 06:27
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • 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();
};
})();
@Developer27149
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