Is javascript single-threaded or why do I need timeout trickery?

122 views
Skip to first unread message

Larion Vasilkovsky

unread,
May 25, 2014, 3:59:39 PM5/25/14
to ang...@googlegroups.com
I have seen it mentioned it in AngularJS books. In AngularJS directives Alex Vanston writes that it is a good practice to use setTimeout trickery (at least in directives) without a full explanation for why?  Just a good practice... Hmm, I am puzzled here. I was under impression that javascript in the browser runs in a single thread. But maybe I am wrong?
This is not the first time I have experineced this problem.
So I have a directive with an isolated scope where I assign a two-way binding to the parent scope model and function binding to the parent controller method.
It looks like this:

        scope: {
          model: '=',
          switched: '&'
        }, 
        template: '<div class="switch" ng-click="toggle()"></div>',
        link: function(scope, elem, attrs) {
          scope.toggle = function() {
            if (!scope.disabled) {
              scope.model = !scope.model;
              //setTimeout(function() {
                scope.switched();
              //});
            }
          };
        }
So when I click on my directive element the toggle method is called and there the scope.model, which is referencing the parent scope.some.some.model is modified. Then I call the method on the parent scope.
Everything works as intended with method calls. I can see that the scope.model is properly modified but when I step into my parent switched method the scope.some.some.model is still has the old value.
If I wrap the call to switched in a timeout and put a breakpoint in my parent switched method the scope.some.some.model is properly updated. So directive's scope.model is a reference to the parent scope.some.some.model and supposed to change it right away before the call to the switched is made, but alas: this is not the case. So what is the reasonable explanation for this behavior? Why some books recommend using setTimeout?
Thanks for your help guys!!!

Sander Elias

unread,
May 26, 2014, 1:04:36 AM5/26/14
to ang...@googlegroups.com

Hi Larion,

You are right, javascript runs in a single thread. Personally I don’t think a setTimeout is a particularly good practice! Sometimes you just have to use it, but its often a source of confusion.
At the core of this is the AngularJS digest system. For some changes it takes a full digest cycle to propagate through your system. There are several different causes for this,
and without seeing your full code, I can’t give you one.
I think using $evalAsync is a better way to solve this.
try this in your code:

   scope.$evalAsync('switched()');

note the quotes!
Basicly it does the same thing, but it comes with a guarantee (timeout does not!) that a digest cycle will occur after your function!

Regards
Sander

Larion Vasilkovsky

unread,
May 26, 2014, 1:38:15 AM5/26/14
to ang...@googlegroups.com
Hi Sander,
Thank you for the response. So you are saying that what looks like a straight JavaScript code where one variable referencing a property in another object on a controller's scope object when being assigned some value is being proxied through angularjs digest 'pipeline' and that is why it is not ready when in the next step I make a call into the same controller and discover that this property on the scope still holds the old value. But when I delay my function call angularjs digest cycle is done and my property finally gets a new value. I was under impression that this is still pure JavaScript stuff and angularjs just watches all the scopes for changes to update all the data bindings.
Thanks again for your response!!!

Sander Elias

unread,
May 26, 2014, 2:39:00 AM5/26/14
to ang...@googlegroups.com
Hi Larion,

You are right, it is just javascript. AngularJS is also javascript, so it can't run a digest cycle between 2 statements is it?
example in a directive I do:

```javascript
 scope.model = 'blah'; // model is bound to something else way up in the hierarchy.
 // here is no time for angular to run anything! no way for it to update anything!
 scope.doSomething(); // doSomething is also way up in the hierarchy  but on an other place then model..
```
It is not only javascript that suffers from this. All code that runs in a single thread in any language will have the same issue.

Regards
Sander


Eric Eslinger

unread,
May 26, 2014, 10:48:47 AM5/26/14
to ang...@googlegroups.com
One problem is - does the doSomething() take a long time? Hard to say from looking at it, but if you put that call in a click handler and it does take a long time to resolve, you'll be blocking execution Everywhere Else on the page (single-threaded and all). That's generally a good reason to put things in a $timeout, IMO.

It's worth noting that $timeout is better to use here than setTimeout, as it wraps the timed-out call in a scope.apply to make sure that angular is notified of any changes you make to models inside that call.

That said, in general I've not had issues with calling functions passed in to a directive using & parameters. Sander's also right - if you need a digest cycle to run in there, you need to release control of the thread and let angular do its thing. Cooperative multitasking, basically.


--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular.
For more options, visit https://groups.google.com/d/optout.

Sander Elias

unread,
May 26, 2014, 11:30:11 AM5/26/14
to ang...@googlegroups.com
Hi Eric,

The doSomething was just a pseudo code example;) Nevertheless, it really does not matter if it takes a long time (if it is a really computing intensive task, it should be carried over to a web-worker!)
Both the timeout and the asynceval set it to execute at a later time, but both do execute it, so there is no performance gain from either method. Both will add it at the end of the execution-chain.
For performance (in a click handler!) its even better to not do a timeout/asynceval, as this is marginally adding load to the task. Either way, its hogging the execution-chain. All javascript will be executed before the next event gets processed.
 There is no escape from that. (well, other than the earlier named web-workers!) 
Usually one does not need to worry about those issues, Javascript is really fast nowadays. Unless traversing large amounts of data/DOM, performance is seldom an issue!

Regards
Sander




Larion Vasilkovsky

unread,
May 26, 2014, 12:23:30 PM5/26/14
to ang...@googlegroups.com
Hi Sander and Eric,
Here are my findings and solutions:

scope: {
          model: '=',
          switched: '&'
        }, 
        template: '<div model="alerts.volume.threshold" class="switch" ng-click="toggle()"></div>',
        link: function(scope, elem, attrs) {
          scope.toggle = function() {
            if (!scope.disabled) {
              scope.model = !scope.model;
              //setTimeout(function() {
                scope.switched();
              //});
            }
          };
        }

My scope.model inside directive was bound by value despite that it is a property inside a object inside an another object. So pass the line scope.model = !scope.model only model in isolated scope was changed. And then because there is  watch on it due to its use in ng-class it was updating the original proper in the controller, but after the digest loop was done. My mistake or assumption was that binding scope.model to "alerts.volume.threshold" would bind it by reference and of course that right after scope.model = !scope.model line the original value is also changed. When I bound to "alerts.volume object and just for testing purposes would modify my line to scope.model.threshold = !scope.model.threshold everything was as I expected it. But this is tight coupling of course and this is no-no:)
The reason i used setTimeout and not $timeout is laziness to inject $timeout in the rush of debugging. 
Sander, regarding to $evalAsync it did not work when wrapped in single quotes, but worked without them. But anyway the original value was still bound to the old value inside my controller, with $timeout on the other hand the value was bound to the new one. 
Here is some summary about evalAsync and timeout differences from this qa on stack overflow.com:


To summarize:

  • if code is queued using $evalAsync from a directive, it should run after the DOM has been manipulated by Angular, but before the browser renders
  • if code is queued using $evalAsync from a controller, it should run before the DOM has been manipulated by Angular (and before the browser renders) -- rarely do you want this
  • if code is queued using $timeout, it should run after the DOM has been manipulated by Angular, andafter the browser renders (which may cause flicker in some cases)
Thank you all!!!
Reply all
Reply to author
Forward
0 new messages