Reputation: 245
So i am using this guide to learn about async-ish behavior in JS. The example i am unable to wrap my head around is this
function asyncify(fn) {
var orig_fn = fn,
intv = setTimeout( function(){
intv = null;
if (fn) fn();
}, 0 )
;
fn = null;
return function() {
// firing too quickly, before `intv` timer has fired to
// indicate async turn has passed?
if (intv) {
fn = orig_fn.bind.apply(
orig_fn,
// add the wrapper's `this` to the `bind(..)`
// call parameters, as well as currying any
// passed in parameters
[this].concat( [].slice.call( arguments ) )
);
}
// already async
else {
// invoke original function
orig_fn.apply( this, arguments );
}
};
}
Usage :
function result(data) {
console.log( a );
}
var a = 0;
ajax( "..pre-cached-url..", asyncify( result ) );
a++;
So conceptually I understand it is attempting to force a function which would execute immediately , to run on the next tick of the event loop, which allows the variable a to be set to the value 1 and hence will always print 1.
The setTimeout section , is the part where I presume , it pushes the call to the next tick of the event loop. However, I am completely lost on the return section of the code.
Q) I know intv will be the timer id, what on earth is the black magic of orgin_fn.bind.apply mean. I know bind , ahem binds a value to a function to be called later , apply passes a this object and and argument array, but I am having a hard time understanding the entire invocation flow and I have never seen fb.bind.apply.
Q)The this object resolves to the Window when i run the code in the browser, why are all the arguments being concatenated on the window (global in this case) object.
Q)I understand this is POC, but is there a legitimate reason to have an else block here which executes the function any way. In which scenario do i see this being executed.
Cheers!
Upvotes: 1
Views: 210
Reputation: 1075815
The orig_fn.bind.apply
thing is in the replacement for the synchronous callback. It's creating a new function that, when called, will call the original function with the same this
and arguments it (the replacement) was called with, and assigning that function to fn
. This is so that later when the timer goes off and it calls fn
, it calls the original function with the correct this
and arguments. (See Function#bind
and Function#apply
; the tricky bit is that it's using apply
on bind
itself, passing in orig_fn
as this
for the bind
call.)
The if
/else
is so that if the replacement is called before the timer goes off (intv
is truthy), it doesn't call orig_fn
right away, it waits by doing the above and assigning the result to fn
. But if the timer has gone off (intv
is null
and thus falsy), it calls the original function right away, synchronously.
Normally, you wouldn't want to create a function that's chaotic like that (sometimes doing something asynchronously, sometimes doing it synchronously), but in this particular case, the reason is that it's ensuring that the function it wraps is always called asynchronously: If the function is called during the same job/task* as when asyncify
was called, it waits to call the original function until the timer has fired; but if it's already on a different job/task, it does it right away.
A more modern version of that function might use a promise, since in current environments, a promise settlement callback happens as soon as possible after the current job; on browsers, that means it happens before a timer callback would. (Promise settlement callbacks are so-called "microtasks" vs. timer and event "macrotasks." Any microtasks scheduled during a macrotask are executed when that macrotask completes, before any previously-scheduled next macrotask.)
* job = JavaScript terminology, task = browser terminology
Upvotes: 1