A deeper look at event loop (micro/macro tasks)

One common question

(function test() {
    setTimeout(function() {console.log(4)}, 0);
    new Promise(function executor(resolve) {
        console.log(1);
        for( var i=0 ; i<10000 ; i++ ) {
            i == 9999 && resolve();
        }
        console.log(2);
    }).then(function() {
        console.log(5);
    });
    console.log(3);
})()

So why the result is 1,2,3,5,4 rather than 1,2,3,4,5

If we look at the detail, looks like the async of setTimeout is different from the async of Promise.then, at least they are not in the same async queue.

The answer is here in the whatwg SPEC.

  • An event loop has one or more task queues.(task queue is macrotask queue)
  • Each event loop has a microtask queue.
  • task queue = macrotask queue != microtask queue
  • a task may be pushed into macrotask queue,or microtask queue
  • when a task is pushed into a queue(micro/macro),we mean preparing work is finished,so the task can be executed now.

And the event loop process model is as follows:

when call stack is empty,do the steps-

  1. select the oldest task(task A) in task queues
  2. if task A is null(means task queues is empty),jump to step 6
  3. set “currently running task” to “task A”
  4. run “task A”(means run the callback function)
  5. set “currently running task” to null,remove “task A”
  6. perform microtask queue
    • (a).select the oldest task(task x) in microtask queue
    • (b).if task x is null(means microtask queues is empty),jump to step (g)
    • (c).set “currently running task” to “task x”
    • (d).run “task x”
    • (e).set “currently running task” to null,remove “task x”
    • (f).select next oldest task in microtask queue,jump to step(b)
    • (g).finish microtask queue;
  7. jump to step 1.

a simplified process model is as follows:

  1. run the oldest task in macrotask queue,then remove it.
  2. run all available tasks in microtask queue,then remove them.
  3. next round:run next task in macrotask queue(jump step 2)

something to remember:

  1. when a task (in macrotask queue) is running,new events may be registered.So new tasks may be created.Below are two new created tasks:
    • promiseA.then()’s callback is a task
      • promiseA is resolved/rejected:  the task will be pushed into microtask queue in current round of event loop.
      • promiseA is pending:  the task will be pushed into microtask queue in the future round of event loop(may be next round)
    • setTimeout(callback,n)’s callback is a task,and will be pushed into macrotask queue,even n is 0;
  2. task in microtask queue will be run in the current round,while task in macrotask queue has to wait for next round of event loop.
  3. we all know callback of “click”,”scroll”,”ajax”,”setTimeout”… are tasks,however we should also remember js codes as a whole in script tag is a task(a macrotask) too.

 

In nodejs world:  setImmediate()is macro/task, and process.nextTick() is a micro/job

 

One good discussion in Chinese and blog.

Advertisements

that=this in javascript

Watch Out: Callbacks And Closures Sometimes Do Not Play Nicely

Of course there are always gotchas with JavaScript. Lets define a callback function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
var createCallBack = function() { //First function
    return new function() { //Second function
        this.message = "Hello World";

        return function() { //Third function
            alert(this.message);
        }
    }
}

window.onload = createCallBack(); //Invoke the function assigned to createCallBack

Lets examine the above code. There are three function operators:

  1. The first function operator creates a function object and is assigned tocreatCallBack.
  2. The second function operator uses the constructor invocation pattern to create a new object.
  3. The third function operator is the result of the return statement. It is a function object and because it is an instance of Object, it gets returned instead of a new object (even though though the function was invoked with new – read constructor invocation pattern for more info).

When invoking the createCallBack variable, what gets passed to the window.onloadevent handler is the result of what is returned by the seond function operator, which is the code in the third function operator. Did you get that? Read this paragraph again until you understand, and if not, stick this code into your browser and play with it.

The above code looks alright, except that undefined gets alerted to the screen (try it for yourself). It turns out that when an event handler invokes a callback function, the function invocation pattern is used. This results in the this parameter being bound to the global object (it is one of the pitfalls of the function invocation pattern) instead of to the object that was created with the constructor invocation pattern. To note that this problem only occurs when one uses the constructor invocation pattern with callbacks like I have illustrated above. To get around this, apply the standard fix for function invocation by declaring a that variable to point to this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var createCallBack = function() { //First function
    return new function() { //Second function
        var that = this;
        this.message = "Hello World";

        return function() { //Third function
            alert(that.message);
        }
    }
}

window.onload = createCallBack(); //Invoke the function assigned to createCallBack

Since the function invoked by the event handler (the third function) is a closure, it has access to the that variable (which is private), and so it has access to the new object created by the constructor invocation pattern.

Read More

 

JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments变量中对应的值,如果 arguments变量中没有对应值,则该形参初始化为 undefined。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解 JavaScript 中的变量作用域非常重要,鉴于篇幅,我们先不在这里讨论这个话题。最后为 this变量赋值,如前所述,会根据函数调用方式的不同,赋给 this全局对象,当前对象等。至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取。

javascript callback with browser dom

prototype is the function template when new is called so that it could be assign to the object’s __proto__.

very good video explaining event loop/callback and browswer.

Some summary:

Typical type of event: User event, XHR event, Timer event.

the whole picture is callstack+eventloop+web api.

call stack is where the code runs

eventloop is where the callback getting queened and executed one by one.

webapi is the browswer provided run time for the timer/asyn callback etc so that when timer is up or async call comes back, it pushes the callback to the queue.

According to Miscov in this video about javascript, the async call back will be pushed to the the event queue once it comes back.

call stack has higher priority than the event loop, which means when there is still code in the callstack, the call backs in the queue will not be executed, so do not put too many stuff into the queue stack. What’s more, the execution of the callstack will block the UI(browser rendering queue), which is bad. So a good practise is to put things in the callback and let it run in the event loop, this way the ui would get a chance to be refreshed between events.

an example of infinite loop

var data; //is initialized to undefined
$http.get(url).success(function(response){
 data=response;
});
while(!data){}; //infinite loop
doSomethingAfterWeGetData();

This is a good example from this video explaining event loop. It becomes an infinite loop since success is registered as a callback which will be executed after the callstack is cleared. so data will not be set and loop will go forever. So the right thing to do is to put the doSomethingAfterWeGetData() method into the callback.

The pseudo code for browser

while(true)
{
  var event = waitforEvent();
  try
  {
    event();
  }catch(){};
  reDrawDom();
}

a more detail version

while(true)
{
  var event = eventQueue.pop();
  if(!event) {contine;}
  try
  {
    var handlers = eventHandlerTable.findAllHandlersFor(event);
    handlers.forEach(function(handler){
     handler.handle(event);
    });
  }catch(){}
  reDrawDom();
}

A detail explaination

Below the covers, javascript has an event queue. Each time a javascript thread of execution finishes, it checks to see if there is another event in the queue to process. If there is, it pulls it off the queue and triggers that event (like a mouse click, for example).

The native code networking that lies under the ajax call will know when the ajax response is done and an event will get added to the javascript event queue. How the native code knows when the ajax call is done depends upon the implementation. It may be implemented with threads or it may also be event driven itself (it doesn’t really matter). The point of the implementation is that when the ajax response is done, some native code will know it’s done and put an event into the JS queue.

If no Javascript is running at the time, the event will be immediately triggered which will run the ajax response handler. If something is running at the time, then the event will get processed when the current javascript thread of execution finishes. There doesn’t need to be any polling by the javascript engine.

Because all outside events go through the event queue and no event is ever triggered while javascript is actually running something else, it stays single threaded.

understanding and using jsonp

JSONP is really a simple trick to overcome the XMLHttpRequest same domain policy. (As you know one cannot send AJAX (XMLHttpRequest) request to a different domain.)

So – instead of using XMLHttpRequest we have to use script HTML tags, the ones you usually use to load js files, in order for js to get data from another domain. Sounds weird?

Thing is – turns out script tags can be used in a fashion similar to XMLHttpRequest! Check this out:

script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://www.someWebApiServer.com/some-data';

You will end up with a script segment that looks like this after it loads the data:

<script>
{['some string 1', 'some data', 'whatever data']}
</script>

However this is a bit inconvenient, because we have to fetch this array from script tag. So JSONP creators decided that this will work better(and it is):

script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://www.someWebApiServer.com/some-data?callback=my_callback';

Notice the my_callback function over there? So – when JSONP server receives your request and finds callback parameter – instead of returning plain js array it’ll return this:

my_callback({['some string 1', 'some data', 'whatever data']});

See where the profit is: now we get automatic callback (my_callback) that’ll be triggered once we get the data.
That’s all there is to know about JSONP: it’s a callback and script tags.

NOTE: these are simple examples of JSONP usage, these are not production ready scripts.

Basic JavaScript example (simple Twitter feed using JSONP)

<html>
    <head>
    </head>
    <body>
        <div id = 'twitterFeed'></div>
        <script>
        function myCallback(dataWeGotViaJsonp){
            var text = '';
            var len = dataWeGotViaJsonp.length;
            for(var i=0;i<len;i++){
                twitterEntry = dataWeGotViaJsonp[i];
                text += '<p><img src = "' + twitterEntry.user.profile_image_url_https +'"/>' + twitterEntry['text'] + '</p>'
            }
            document.getElementById('twitterFeed').innerHTML = text;
        }
        </script>
        <script type="text/javascript" src="http://twitter.com/status/user_timeline/padraicb.json?count=10&callback=myCallback"></script>
    </body>
</html>

Basic jQuery example (simple Twitter feed using JSONP)

<html>
    <head>
        <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
        <script>
            $(document).ready(function(){
                $.ajax({
                    url: 'http://twitter.com/status/user_timeline/padraicb.json?count=10',
                    dataType: 'jsonp',
                    success: function(dataWeGotViaJsonp){
                        var text = '';
                        var len = dataWeGotViaJsonp.length;
                        for(var i=0;i<len;i++){
                            twitterEntry = dataWeGotViaJsonp[i];
                            text += '<p><img src = "' + twitterEntry.user.profile_image_url_https +'"/>' + twitterEntry['text'] + '</p>'
                        }
                        $('#twitterFeed').html(text);
                    }
                });
            })
        </script>
    </head>
    <body>
        <div id = 'twitterFeed'></div>
    </body>
</html>

JSONP stands for JSON with Padding. (very poorly named technique as it really has nothing to do with what most people would think of as “padding”.)

with angularjs

AngularJS looks at the outgoing URL and replaces the phrase “JSON_CALLBACK” with an auto-generated callback name. If you make the same JSONP request a few times in a row, you’ll notice that the callback name increments in the URL:

?callback=angular.callbacks._0
?callback=angular.callbacks._1
?callback=angular.callbacks._2
?callback=angular.callbacks._3

As you can see, AngularJS generates functions that are accessible off the global angular namespace.

angular.module('', []);

function DataCtrl($scope, $http) {
  
  $http.jsonp("http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero").
  success(function(data) {
    $scope.data = data;
    $scope.name = data.name;
    $scope.salutation = data.salutation;
    $scope.greeting = data.greeting;
  }).
  error(function (data) {
    $scope.data = "Request failed";
  });
}

HTML:

<div id="wrapper" ng-app>
  <div ng-controller="DataCtrl">
    <pre ng-model="data">
     {{data}}
    </pre>
    
    <input ng-model='name' />
    {{name}}
    
    <input ng-model='salutation' />
    {{salutation}}
    
    <input ng-model='greeting' />
    {{greeting}}
  </div>
</div> 

javascrip callback

How Callback Functions Work?

Because functions are first-class objects in JavaScript, we can treat functions like objects, so we can pass functions around like variables and return them in functions and use them in other functions. When we pass a callback function as an argument to another function, we are only passing the function definition. We are not executing the function in the parameter. We aren’t passing the function with the trailing pair of executing parenthesis () like we do when we are executing a function.

And since the containing function has the callback function in its parameter as a function definition, it can execute the callback anytime. This allows us to execute the callback functions at any point in the containing function.

It is important to note that the callback function is not executed immediately. It is “called back” (hence the name) at some specified point inside the containing function’s body. So, even though the first jQuery example looked like this:

//The anonymous function is not being executed there in the parameter. 
//The item is a callback function
$("#btn_1").click(function() {
  alert("Btn 1 Clicked");
});

The anonymous function will be called later inside the function body. Even without a name, it can still be accessed later via the arguments object by the containing function.

 

Defining Functions with Callback Arguments

So far we have been looking at the “client” side where the call originates and the callback function is defined. Now let’s look at the other side, where the original call gets processed, and from where the callback is invoked.

Here is a simple maths function that generates a random number between two values, min and max, and passes the result to a callback function:

1
2
3
4
5
function randomGenerator(min, max, callback)
{
   var myNumber = Math.floor(Math.random() * (max - min + 1)) + min;
   setTimeout(function() { callback(myNumber); }, 500);
}

This maths function accepts three arguments, the third of which is the callback function to which the results should be sent. The function first does the required calculation (using the values of the first two arguments), then we make it wait 500 milliseconds (usingsetTimeout) before invoking the callback function passing the result.

(We are using setTimeout here merely to simulate what might happen with a complex asynchronous computation of a random number — imagine how slow your app could become if this function is called multiple times and you had to wait each time for the result via a standard return from the called function.)

On the client side, we can now make use of our random number generation function like this:

1
2
3
4
randomGenerator (5, 15, function(num)
{
   console.log("Your number is " + num);
});

Five hundred or so milliseconds later our random number will be passed to our callback function and written to the console.

 

Closures

A callback function can be used as a closure. A closure is a function that remembers references to variables outside of it’s local scope. By this we mean variables that are neither parameters of the function, nor local variables defined inside the function with var keyword, but variables defined outside of the function, for example global variables or local variables defined in the containing function. Here is an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Global variable.
var name = "Chris";
function lottery()
{
   // Local variables.
   var win = ", you win!";
   var lose = "Better luck next time ";
   // The function in the third parameter is a closure
   // that refers to the variables name, win, lose.
   randomGenerator (0, 100, function(num)
   {
      if (num > 50)
      {
         alert(name + win);
      }
      else
      {
         alert(name + lose);
      }
   });
}

In the above example, the function randomGenerator will return more or less instantly, and then the function lottery will return instantly. Then, 500 milliseconds later, the callback will be invoked. This means that, even though the function lottery is no longer running, the local variables win and lose are available to the callback, since it is a closure function. All functions in JavaScript have the capability to be closures that remember references to external variables.

 

FROM HERE AND HERE

async callback in GWT

The Asynchronous Interface

The asynchronous interface is the one that your client-side code will use. Your code will call the service but not wait for a reply (hence the ‘asynchronous’ of course). The reply will go somewhere else.

That ‘somewhere else’ is a method in the client-side code and you have to tell the gizmo that does all the hard work where this method is. Since the calling code is not going to deal with the callback, the return type of the calling must be void. In order for the system to know where to send the result of the call, the calling code passes it the routine.

So, in clientCallsTheServer you call methodOnTheServer passing it (in our case) a string and a callbackMethod and then it goes on its way without waiting for an answer.

Some time later, the callbackMethod is run with the result of the call. Here’s our asynchronous interface. It MUST have the same name as the synchronous (server-side) interface, but with ‘Async’ tagged on the end. It must also be in the same place as the synchronous interface, i.e. somewhere inside the ‘client’ bit of your project.

public class RPCDemo extends Composite
{
    VerticalPanel panel = new VerticalPanel();

    public RPCDemo()
    {
        initWidget(panel);
        panel.setHorizontalAlignment(HasAlignment.ALIGN_CENTER);
        panel.setWidth("100%");
        Button button = new Button("Test RPC", new ClickListener()
        {
            public void onClick(Widget sender)
            {
                while (panel.getWidgetCount() > 1)
                    panel.remove(1);
                RPCService rpc = new RPCService();
                rpc.testRPC("Hello", callback);
            }
        });
        panel.add(button);
        panel.setCellWidth(button, "100%");
    }
    AsyncCallback callback = new AsyncCallback()
    {
        public void onFailure(Throwable caught)
        {
            panel.add(new HTML("Failed:" + caught.getMessage()));
        }

        public void onSuccess(Object result)
        {
            while (panel.getWidgetCount() > 1)
                panel.remove(1);
            panel.add(new HTML((String) result));
        }
    };
}

From Here