You-dont-know-js: "async & performance": Trying to save callbacks example

Created on 11 Mar 2015  路  11Comments  路  Source: getify/You-Dont-Know-JS

Hi,

I am trying to get my head round the timeoutify() function which is used to solve the trust issue of never being called

What about the trust issue of never being called? If this is a concern (and it probably should be!), you likely will need to set up a timeout that cancels the event. You could make a utility (proof-of-concept only shown) to help you with that:

function timeoutify(fn,delay) {
    var intv = setTimeout( function(){
            intv = null;
            fn( new Error( "Timeout!" ) );
        }, delay )
    ;

    return function() {
        // timeout hasn't happened yet?
        if (intv) {
            clearTimeout( intv );
            fn.apply( this, arguments );
        }
    };
}

// using "error-first style" callback design
function foo(err,data) {
    if (err) {
        console.error( err );
    }
    else {
        console.log( data );
    }
}

ajax( "http://some.url.1", timeoutify( foo, 500 ) );

From my understanding, fn is required to be executed once within 500ms, if it is fired after that given time, an err obj would be passed on fn callback. But I recalled in chapter 1, A JavaScript program is (practically) always broken up into two or more chunks, where the first chunk runs *now* and the next chunk runs *later*, in response to an event. I would describe the timeoutify function as the following

// now
function timeoutify(fn, delay) {
  ...
  return function {
    // timeout hasn't happened yet?
    if (intv) {
      clearTimeout( intv );
      fn.apply( this, arguments );
    }
  }
}

// later
var intv = setTimeout( function(){
            intv = null;
            fn( new Error( "Timeout!" ) );
        }, delay );

As the result of that, fn( new Error( "Timeout!" ) ) is never called because of clearTimeout function. Please correct my mistake and thank you for reading!

question

Most helpful comment

apply(..) calls the function in context (that is, bind(..) here) but with a variable number of arguments.

The first param to apply(..) is a this binding for the call (again, bind(..) here)... normally, you'd be saying orig_fn.bind(..) which means that bind(..)'s this would be the function orig_fn. So when we call apply(..), orig_fn is the this to be used.

Then we construct the array of params that should be passed to bind(..) by apply(..). Since bind(..) wants as its first param a this to use for the orig_fn call, the first element in our array is set as this with [this]. Then, we add on any of the rest of the arguments that have been passed in (in the call to the enclosing return function() {.. function), by turning arguments into a real array with [].slice.call(arguments). Then we use concat(..) to combine those two arrays. After the first param to bind(..), all subsequent ones (the rest of the array) are taken as curried/partially applied params (that is, preset).

The end result of the bind(..) call (which, again, we made by using apply(..) so we could pass in a variable amount of params with the array) is a hard-bound, and in this case also partially applied (sorta aka "curried"), function, but one that hasn't been called itself yet. We store that in fn.

Later, this hard-bound and curried function can just be called with fn(), as is done with if (fn) fn();.


Basically, if the attempt is made to call your function too early (that is, synchronously), we just capture the entire attempt to call the function (its this and any arguments) and save that into an fn that we can call "later", in the timeout callback.

All 11 comments

From my understanding, fn is required to be executed once within 500ms

No. fn(..) is not being externally executed. timeoutify(..) controls the execution of fn(..).

What needs to be fired externally before the 500ms mark is the function we return with return function() {.... Either _that_ function gets called before the 500ms mark, in which case it clears the intv timeout and then calls fn(..), OR that callback hasn't run externally early enough (or never), and instead the timeout function callback setTimeout( function(){... will fire first, in which case a subsequent external call to the callback is going to be ignored.

...if it is fired after that given time, an err obj would be passed on fn callback.

Not quite. In fact, the inner fn(..) is always going to get called by the 500ms mark, one way or the other. If it's called from external execution, great. If not, it's instead called at the 500ms point by the timeout callback. But in this latter case, the external execution, if it ever happens, is now going to be completely moot, since fn(..) already got executed.

As far as the now/later thing, return function() {... in fact happens _now_, but the execution of that function doesn't. So in this case, both functions are _later_ chunks. The question is which one is less _later_ than the other.

:+1: @getify, I'm pretty clear now.

Would you mind elaborate these line of code for me in the asyncify 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))
  );
}

I could understand bind and apply but I have no idea of why you are using bind.apply chaining method here (first time I see this combination). Could you please explain in layman term and/or transform these complicated codes into sth that I could understand

apply(..) calls the function in context (that is, bind(..) here) but with a variable number of arguments.

The first param to apply(..) is a this binding for the call (again, bind(..) here)... normally, you'd be saying orig_fn.bind(..) which means that bind(..)'s this would be the function orig_fn. So when we call apply(..), orig_fn is the this to be used.

Then we construct the array of params that should be passed to bind(..) by apply(..). Since bind(..) wants as its first param a this to use for the orig_fn call, the first element in our array is set as this with [this]. Then, we add on any of the rest of the arguments that have been passed in (in the call to the enclosing return function() {.. function), by turning arguments into a real array with [].slice.call(arguments). Then we use concat(..) to combine those two arrays. After the first param to bind(..), all subsequent ones (the rest of the array) are taken as curried/partially applied params (that is, preset).

The end result of the bind(..) call (which, again, we made by using apply(..) so we could pass in a variable amount of params with the array) is a hard-bound, and in this case also partially applied (sorta aka "curried"), function, but one that hasn't been called itself yet. We store that in fn.

Later, this hard-bound and curried function can just be called with fn(), as is done with if (fn) fn();.


Basically, if the attempt is made to call your function too early (that is, synchronously), we just capture the entire attempt to call the function (its this and any arguments) and save that into an fn that we can call "later", in the timeout callback.

:+1: @getify, tks you for your clarification :grinning:

just for your reference, I found this asyncify-simple function more intuitive

// asyncify

function asyncify(syncFn) {
  return function() {
    var args = Array.prototype.slice.call(arguments)
    var callback = args.pop()
    setImmediate(function() {
      try {
        callback(null, syncFn.apply(this, args))
      } catch (error) {
        callback(error)
      }
    })
  }
}

module.exports = asyncify

// usage
var asyncify = require("simple-asyncify")

function addSync(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("add requires two numbers")
  }
  return a + b
}

var add = asyncify(addSync)

add(1, 2, function(error, sum) {
  console.log("The sum is: " + sum)
})

add(1, function(error, sum) {
  console.log(error.message)
})

console.log("Let鈥檚 do some math!")

// Let鈥檚 do some math! 
// The sum is: 3 
// add requires two numbers 

My asyncify(..) does something quite different than the one you've shown, which is why the code is quite different (and simpler, since their task is much simpler). These two different functions are solving different problems, despite the name being the same.

Their's produces a callback-accepting function wrapper around a non-callback sync function. The goal is to turn any sync function into an callback-based async one, such that you can call it, it will execute asynchronously, and it will call your callback with the results.

You use their's with a utility directly.

Mine is designed to wrap any function in an async switch. The wrapper, if called "now" (aka synchronously), will delay the underlying function until the next event loop. If the wrapper is called "later" (aka asynchronously), the switch has already flipped, so the underling function is allowed to proceed unimpeded.

You use mine to wrap a callback being passed to another utility, if you're not sure if that utility may synchronously call the callback you give it.

awesome answer @getify, tks you, I learned a lot today!!!

Asyncify function.
Are these chunks equivalent?

if (intv) {
  fn = orig_fn.bind.apply( // <---
    orig_fn,
    [this].concat([].slice.call(arguments))
  );
}

vs

if (intv) {
  fn = Function.prototype.bind.apply( // <---
    orig_fn,
    [this].concat([].slice.call(arguments))
  );
}

@JohnGurin yes those should be equivalent.

@getify in asyncify function
Can move fn=null; like follows?

function asyncify(fn) {
var orig_fn = fn;
fn = null;
var intv = setTimeout( function(){
intv = null;
if (fn) fn();
}, 0 );
...

Was this page helpful?
0 / 5 - 0 ratings