3

As far as I know only the declaration part of a function expression gets hoisted not the initialization. E.g.:

var myFunction = function myFunction() {console.log('Hello World');};

So "var myFunction;" gets hoisted, but "function myFunction()..." not.

Now to my question, I played a little bit around with the google auth functions:

"use strict";

$(document).ready = (function() {
  var clientId = 'MYCLIENTID';
  var apiKey = 'MYAPIKEY';
  var scopes = 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.readonly https://www.googleapis.com/auth/drive.appfolder https://www.googleapis.com/auth/drive.apps.readonly https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/drive.install https://www.googleapis.com/auth/drive.metadata https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/drive.photos.readonly https://www.googleapis.com/auth/drive.scripts';

  $('#init').click(function() {
    gapi.client.setApiKey(apiKey);
    window.setTimeout(checkAuth(false, handleAuthResult), 1);
  });

  var checkAuth = function checkAuth(imm, callback) {
    gapi.auth.authorize({
      client_id: clientId,
      scope: scopes,
      immediate: imm
    }, callback);
  };

  var handleAuthResult = function handleAuthResult(authResult) {
    if (authResult) {
      gapi.client.load('drive', 'v2', initialize);
    } else {
      $('#progress').html('Anmeldung fehlgeschlagen');
    }
  };

  // Other code
})();

On line 10 "window.setTimeout(checkAuth..." I call the checkAuth function which is declared below this function call. My assumption was that I get an error saying "...checkAuth is not a function / undefined etc. ...", but instead it worked. Could someone please explain this to me?

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
JShinigami
  • 7,317
  • 3
  • 14
  • 22
  • *"As far as I know only the declaration part of a function expression gets hoisted not the initialization."* Function expressions don't have a declaration part. `var checkAuth = ...;` is a **variable declaration**. The value you assign doesn't matter. It can be a function (like here) or a number or something else. Variable declarations are hoisted. – Felix Kling Nov 20 '15 at 18:01
  • `window.setTimeout(checkAuth(false, handleAuthResult), 1);` This must be written as `window.setTimeout(checkAuth.bind(null, false, handleAuthResult), 1);` and everything will be alright. More explanation below by other authors. – Vijay Dev Nov 20 '15 at 18:02
  • The order in the source code doesn't matter that much.The order in which the code is executed is what you need to look at. – Felix Kling Nov 20 '15 at 18:03

2 Answers2

2

That's because when an actual click event on the element is triggered, the value of checkAuth is then available in the scope. The error you expected would happen this way:

checkAuth(false, ...); // the symbol is available, but...

// its value is assigned here
var checkAuth = function checkAuth() {
    /* more code */
};

Notice the immediate invocation of checkAuth() before it's assigned in the above snippet.

What is available at the point of invocation is the symbol named checkAuth; its value, though, gets assigned later. Hence the error checkAuth is not a function rather than checkAuth is undefined.

Linus Kleen
  • 33,871
  • 11
  • 91
  • 99
  • This answer is incorrect. In fact the symbol will be available in javascript and the snippet will not throw the error you mention. – lorefnon Nov 20 '15 at 18:02
  • Thank you for the swift answer. But why is it available in the scope? The click function has its own scope, or not? – JShinigami Nov 20 '15 at 18:03
  • I am sorry, but I need to dig deeper. I still do not get why the value of checkAuth is available in the scope, when I use a click event? Can you please explain it in more detail? – JShinigami Nov 20 '15 at 18:31
  • @Shimigami: Every function in JS is a closure. Every function has access to variables defined in a "higher" scope. `checkAuth` is defined in the same scope the handler is defined, hence the handler can access that function. – Felix Kling Nov 20 '15 at 21:21
  • 1
    @lorefnon [Is that so?](https://jsbin.com/wivabulanu/edit?js,console) Shimigami, have a look at [this answer](http://stackoverflow.com/a/7506937/255756) why the *symbol* is available, but its *value* is available later. – Linus Kleen Nov 21 '15 at 08:53
  • Ah My bad. I was testing for something like [this](http://jsfiddle.net/9s0m539e/) . I did not acknowledge that the variable being assigned had the same name as the function. – lorefnon Nov 21 '15 at 10:07
  • 1
    OF COURSE! The compiler does all the declarations at first (var clientId etc.) and then executes the script: 1) Initializes clientId, apiKey and scopes variables 2) Adds the click event listener 3) Initializes the checkAuth variable 4) Initializes the handleAuthResult variable 5) Other code... When then somebody clicks the button, the checkAuth variable is already initialized (step 3). I did not take into account that the click happens later. Thank you! – JShinigami Nov 21 '15 at 17:07
  • If, in this example, you removed the 'var ' in front of the checkAuth=, you would have gotten a 'checkAuth not defined' error instead of a 'checkAuth is not a function' error. That's because the var-phase didn't create the variable checkAuth at all, and it doesn't exist until the assignment. – OsamaBinLogin Nov 03 '16 at 20:37
1

Declaration of a named function is different from assigning a function to a variable. This article elaborates this in much detail with examples. I am quoting the important parts here for the sake of completeness:

we first need to understand the distinction between a function expression and a function declaration. As it’s name implies, a function expression defines a function as part of an expression (in this case assigning it to a variable). These kind of functions can either be anonymous or they can have a name.

... a function declaration is always defined as a named function without being part of any expression.

... the function expression can only be called after it has been defined while the function declaration can be executed both before and after it’s definition

You will get the error that you expected if you remove the function name :

 var checkAuth = function(imm, callback) {
    gapi.auth.authorize({
      client_id: clientId,
      scope: scopes,
      immediate: imm
    }, callback);
  };

In addition you seem to be using setTimeout incorrectly:

window.setTimeout(checkAuth(false, handleAuthResult), 1);

Will execute the checkAuth immediately rather than delay it, if you want to delay the execution of checkAuth you can wrap it in an anonymous function :

window.setTimeout(function() { checkAuth(false, handleAuthResult) }, 1);
Community
  • 1
  • 1
lorefnon
  • 12,875
  • 6
  • 61
  • 93
  • I tried to remove the function name, but it still works. Yes you are right, I should use an anonymous function within the setTimeout. – JShinigami Nov 20 '15 at 18:20
  • Once you add an anonymous function it will work because by the time the anonymous function runs the function has already been assigned to the variable. However if you try to execute it before assignment it will fail: Contrived example: `foo(); var foo = function() { alert('hello'); } ;` `VM380:2 Uncaught TypeError: foo is not a function(…)` – lorefnon Nov 20 '15 at 18:26
  • I tried to remove the function name and use an anonymous function, but still it works... – JShinigami Nov 20 '15 at 18:37
  • Yes that does not invalidate my answer. Please read my previous comment carefully, it contains the answer for your query. – lorefnon Nov 20 '15 at 18:51
  • If you do not use an anonymous function you will get the error. – lorefnon Nov 20 '15 at 18:52