13 Synchronous and asynchronous programming
13.1 Asynchronous programming (is still possible in NeuroTask Scripting!)
By nature, JavaScript is asynchronous. In practice, this means that the main program (called thread) always continues. It is impossible to have a script wait somewhere for something. So, how can you still do timed operations? Or how do you wait for events like a click? This can be done in ‘vanilla’ JavaScript but usually a library like jQuery is used (preloaded in NeuroTask Scripting), as in the following example:
var c = main.addblock("center","center",20,5,"Click Me!","yellow");
c.node.id = "jantje";
$("#jantje").on("click",function(){
console.log("ONE");
});
console.log("TWO");
awaitkey(" ");
What do we expect to see on the console output? ONE, TWO? No, we will see TWO and then each time you click the yellow block, ONE is added. The script ends when the spacebar is pressed and the yellow block disappears. The construction:
$("#jantje").on("click",function(){
console.log("ONE");
});
is an example of asynchronous programming. This is typically based on having a certain element (e.g., a block) ‘listen’ for an event (here a ‘click’). when the event occurs, a function is called. Such a function is often called a handler. Here, the handler prints “ONE”. Most JavaScript applications are constructed from these asynchronous constructs. They are particularly handy when many events can occur and when the handling is brief and when the order over evens is not very important. This fits many web applications. It does not, however, fit how most experiments work. These are much more akin to recipes as we saw in the beginning of this book. Do this, then do that, then wait a while, etc.
The main weak point of asynchronous-only systems is that they cannot halt the script execution until a certain time has lapsed or until a certain event has occurred. Only synchronous system can do this. So, in ‘vanilla’ JavaScript, if you want to do something after 3000 ms, you must do something like:
setTimeout(function(){
console.log("house");
}, 3000);
This works and waits 3000 ms to write “house” to the console. Now, suppose I wanted to write “dog” after another 3000 ms and then “cat” after another 3000 ms? Then, the code would become:
setTimeout(function(){
console.log("house");
setTimeout(function(){
console.log("dog");
setTimeout(function(){
console.log("cat");
}, 3000);
}, 3000);
}, 3000);
You can see where this is going. Of course, there are ways to make this more readable and maintanable, expecially with modern additions to JavaScript, but these can also be rather complicated compared to simply writing:
await(3000);
console.log("house");
await(3000);
console.log("dog");
await(3000);
console.log("cat");
It is not necessarily the case that this approach to programming is better, but in my opinion it is certainly much better for programming almost all experiments one might encounter in practice. Having said that, as you can see from the first example here, there is nothing keeping you from adding asynchronous constructs with jQuery, like the standard JavaScript setTimeout() function or the jQuery on() function. Especially, if you are making a game in NeuroTask Scripting or have a display with many elements on which you might click, for example, an asynchronous solution might work better.
13.2 The waitfor .. or construction
In the chapter on showing video, we encountered the waitfor statement. This is an extension of JavaScript by StratifiedJS we are using with NeuroTask Scripting. If there are many events than can happen and you do not want to use asynchronous programming, this is a handy construction. Many of the elements of the NeuroTask Scripting libraries have been implemented with it, for example, the timeout aspects.
Suppose, we have an experiment where we present random integers from 1 to 10 to the subject and he has to click on a “yes” or “no” block to answer the question “Is this larger than 5?”. This goes on forever until the subject clicks the “Quit” button or hits the Esc key. Answers have to given within 3000 ms, else a text pops up telling the subject to respond faster. This is a good scenaria to use the waitfor..or statement. Let’s look at the example:
var e, n, response,
left = addblock(10,80,15,10).button("Yes"),
right = addblock(75,80,15,10).button("No");
var esc = addblock(80,5,15,10).button("Quit","navigation","quit","quit_button")
.style("background-color","bisque","#quit_button");
while (true) // i.e., do this forever until a break is encountered
{
n = randint(1,11);
text(n + " is larger than 5");
waitfor
{
e = left.await('click');
response = "yes";
} or {
e = right.await('click');
response = "no";
} or {
awaitkey('ESCAPE');
break;
} or {
esc.await('click');
break;
} or {
await(3000);
text("Please, answer within 3 seconds");
await(1500);
}
text("You clicked " + response);
await(1000);
clear();
}
text("Thanks!");
await(1000);
This demo can also be found at: https://scripting.neurotask.com/howto/waitfor_or_demo.
So, an infinite loop shows a text like “6 is larger than 5” and then one of five things can happen:
- left (yes) block is clicked (there is no scoring in this simplified version)
- right (no) block is clicked
- Esc key is pressed (calls break, which breaks out of the loop, ending the experiment)
- Quit block is clicked (likewise)
- Timeout time of 3000 ms is reached (shows a text “Please, answer within 3 seconds” for 1500 ms)
If any of those events happens (click, keypress, timeout), only that part of the statement is evaluated (within the curly brackets) and all others will remain ignored. This is why they are strung together with or: anything of the events suffice to direct execution there. See https://conductance.io/reference/#sjs:%23language/syntax::waitfor-or for more details.