Event loop, task queue i microtask queue

Event loop, task queue i microtask queue

Na rozmowach na mid/senior developera często pada pytanie o event loop i asynchroniczność. Z pętlą zdarzeń związane są dwie kolejki: task queue i microtask queue. Dzisiejszy wpis będzie opisywał model interakcji event loop z tymi kolejkami.

Task queue

Większość osób spotkała się z tym „dziwnym” przypadkiem, kiedy kod wykonuje się nie w tej kolejności, której byśmy się spodziewali.

const printHello = () => console.log('hello');

setTimeout(printHello, 0);
console.log('Me first');

// at    console
// 0ms   Me first
// 1ms   hello

Odroczenie wywołania funkcji printHello o zero milisekund sprawia, że na konsoli najpierw pojawia się Me first, a dopiero potem hello. Na pierwszy rzut oka nie ma to najmniejszego sensu, ponieważ czekanie na coś zero milisekund to tak faktycznie nieczekanie wcale.

Co się stanie, jeśli pomiędzy użyciem funkcji setTimeout a wyświetleniem na konsole Me first wywołamy jakąś operację trwającą pół sekundy? Czy wtedy hello pojawi się jako pierwsze na konsoli?

const printHello = () => console.log('hello');
// It could be looping over huge arr or something
const blocksFor500ms = () => {}

setTimeout(printHello, 0);

blocksFor500ms();

console.log('Me first');

// at    console
// 500ms   Me first
// 501ms   hello

Znowu pudło. Dlaczego, pomimo tego, że minęło już pół sekundy, funkcja printHello nie została wywołana jako pierwsza?

Dziwny ten cały Javascript… a może jednak nie?

Tym, co tak naprawdę robi setTimeout jest poproszenie przeglądarki o ustawienie Timera na zero milisekund i przekazanie jej funkcji, która po upłynięciu przekazanego czasu ma zostać dodana do Task queue.

Task queue to specjalna kolejka przechowująca zadania, którymi Js musi się zająć. Event loop jest odpowiedzialny, żeby zadania z kolejki znalazły się na callstacku. Event loop sięga po kolejne zadanie w chwili, gdy stos jest pusty. Poniżej możesz prześledzić jak zachowuje się Javascript napotykając wspomniany wcześniej kod.


Microtask queue

Przejdźmy do następnego przykładu kodu.

function printHello() {
  console.log('hello');
}

function printPromise() {
  console.log('promise');
}

setTimeout(printHello, 0);
Promise.resolve().then(printPromise);
console.log('Me first');

// at    console
// 0ms   Me first
// 1ms   promise
// 2ms   hello

W tym przypadku funkcja printPromise, przekazana do wywołania po rozwiązaniu Promise, zostaje wywołana przed funkcją printHello przekazaną do setTimeout. Wraz z ES6 do Javascriptu została wprowadzona nowa kolejka microtask queue. To na nią trafiają funkcje przekazane do wywołania po zakończeniu Promise. Dla event loop zadania z microtask queue mają wyższy priorytet niż zadania z task queue.

Kolejnym faktem, na który trzeba zwrócić uwagę, jest to, że event loop pobiera po jednym zadaniu na raz z task queue, ale jeśli już weźmie się za zadania z microtask queue to nie skończy, dopóki microtask queue nie będzie całkowicie pusta.


Podsumowanie

To był mały wstęp do asynchroniczności i działania event loop. Poniżej znajdziesz świetne materiały, dzięki którym możesz zgłębić ten temat. Polecam zapoznać się ze wszystkimi wymienionymi materiałami w kolejności.

Pobierz darmowy ebook zawierający 20 pytań, które możesz usłyszeć na rozmowie kwalifikacyjnej