-
-
Save bluejava/9b9542d1da2a164d0456 to your computer and use it in GitHub Desktop.
// 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(); | |
}; | |
})(); |
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
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.
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.