1

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!

Bobo
  • 227
  • 2
  • 9

1 Answers1

1

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

T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • I've been under the impression that 'this' object is the context from which the object was called. Is it correct to say that 'this' argument can also be a function (for apply/call)? So I assume in fn.bind.apply , the apply is called first , followed by bind ? or is it just setting the apply property on the bind property. Sorry If i sound confused. – Bobo Nov 07 '17 at 18:41
  • 1
    @RickMota: `this` isn't a context at all, nor is it a scope, though people commonly use those terms in relation to it. `this` is...`this`. :-) It's effectively a somewhat-hidden argument, which is determined by how the function is called (usually). [More here](https://stackoverflow.com/questions/3127429/). Yes, `this` can be a reference to a function, and you're spot-on that when calling `Function#bind`, we want `this` to refer to the function we want to bind (which is why the more normal version `newFunction = someFunction.bind(...)` works; `bind` use `this`, which his set to `someFunction`). – T.J. Crowder Nov 07 '17 at 18:46
  • (Re the *"...determined by how the function is called (usually)..."* above: Arrow functions, and functions that already have a `this` bound to them, ignore any `this` they're called with. Instead, an arrow function *closes over* the `this` in its parent context just like it would a variable declared in that context, and of course, a bound function uses the `this` bound to it.) – T.J. Crowder Nov 07 '17 at 18:47
  • (Re "context": I don't like using "context" for `this`, though it frequently is used that way -- less so now than a few years ago -- because "context" already has a meaning in JavaScript in the form of [execution contexts](https://tc39.github.io/ecma262/#sec-execution-contexts), which are not what `this` is [but to make matters worse, sometimes indirectly contain the value for `this` for the execution context] via the current [lexical environment](https://tc39.github.io/ecma262/#sec-lexical-environments) for the context.) – T.J. Crowder Nov 07 '17 at 18:54
  • Finally (talk about rambling!), FWIW, the best thing to call `this` in my view is a "`this` binding". That's what the spec calls it. ("Bindings" are how JavaScript resolves variables, parameters, declared function names, `this`, `super`, and a few other things. A binding is like an object property, with a name and value, but the object holding them -- the lexical environment record -- is conceptual rather than literal. Probably. It's up to the implementation.) – T.J. Crowder Nov 07 '17 at 19:04