Вы не понимаете логику асинхронности в JS.
Когда родительская функция вызывает setTimeout, ядро JS создаёт таймер и привязывает к нему ссылку на функцию и окружение. После этого родительская функция продолжает свою работу. При срабатывании таймера, привязанная к нему функция с окружением помещается в очередь макрозадач и выполняется, когда до неё дойдёт очередь. Как правило, родительская функция к тому моменту давно уже завершилась.
Примерно то же самое выполняется для любой асинхронной функции с тем исключением, что функции-обещания (Promise) помещаются в приоритетную очередь микрозадач.
Ваш код
function getNum(){
let a;
setTimeout(function(){a = 5;}, 2000);
return a;
}
выполняется как
// Вызов getNum()
let a;
setTimeout(fn, 2000); // Здесь создаётся таймер с привязанной функцией и лексическим окружением, сама функция не вызывается
return a;
...
// Сработал таймер, fn помещена в очередь
// Вызов fn
a = 5;
P.S. Да, в JS фактически один поток выполнения задачи и служебные потоки для таймеров, ввода/вывода и прочих задач ядра. Полноценного параллелизма в нём нет, все функции основного потока выполняются только по очереди.