-
Notifications
You must be signed in to change notification settings - Fork 2
Chaining Functions
One of the more difficult aspects of designing online behavioural experiments is dealing with the sequential order and necessary temporal delays in JavaScript. Using Python (as in OpenSesame, Expyriment, PsychPy, and other projects), it is trivial to carry out steps one after the other, and pause, or wait for a response, as necessary:
# Pseudo-code Python
clear_screen()
sleep(500)
show_text('+')
sleep(500)
clear_screen()
sleep(500)
show_text('Probe text')
wait_for_response(timeout=5000)
However, because JavaScript was designed for creating dynamic websites, it doesn't easily support functions like sleep() or wait_for_response() for very good reasons: you wouldn't want a website to stop everything for half a second, or wait, suspending any other code, for user input.
PsychScript addresses this limitation by adopting a convention of chaining functions together one after another, and by using the JavaScript setTimeout() function.
Rather than defining a trial as one long list of commands to execute, PsychScript breaks the procedure into a series of functions, each one of which finishes calling the next one, either immediately, or at a delay. Take for example the supplied example experiment "lexical_decision_keyboard_response", which presents a series of words in a random order, preceded by a fixation cross, and logs what button participants press on the keyboard in response.
The trial is broken up into two functions. trial_stage0, after choosing the stimuli to show from a list, shows a fixation cross for 1000 msec, and then, using the setTimeout() method, sets trial_stage1() to execute immediately after that.
function trial_stage0() {
deactivate_keyboard_response();
stimuli_number = random_order[trial_number];
probe = probes[stimuli_number];
code = codes[stimuli_number];
show_fixation(1000);
setTimeout(function(){trial_stage1()}, 1000);
};
The second part of the trial, trial_stage1(), then does it's thing: it starts timing in anticipation of a response, it shows the probe text, and it tells the page to log a keyboard response (only if it's a 'y' or 'n').
function trial_stage1() {
start_time = (Date.now());
show_text(probe);
get_keyboard_response('YN');
};
All of this relies on some built-in functions which most users won't need to know about. show_fixation() uses setTimeout() under the hood:
function show_fixation(duration) {
var pause = duration/3
show_text('')
setTimeout(function(){show_text('+')}, pause)
setTimeout(function(){show_text('')}, pause*2)
};
get_keyboard_response() implements chaining in another way, which is a little more complex. Using JavaScript's document.onkeydown() function, it tells the page to wait for a key to be pressed, and once it has, it checks what the button was either 'y' or 'n', and, if so, calls the log_response() function, which in turn, calls trial_stage0(), setting the next trial in motion.
For more advanced designs, you can go under the hood and modify these functions.