JavaScript — pytania rekrutacyjne: przewidź wynik i popraw kod

JavaScript: pytania rekrutacyjne z przewidywaniem wyniku. Sprawdź mikrozadania, domknięcia, ilość z formularza i błędy async w 26 testach Chrome.

Author: PracHub

Published: 10/11/2026

JavaScript — pytania rekrutacyjne: przewidź wynik i popraw kod

October 11, 2026

Quick Overview

Autorskie zadania JavaScript: kolejka mikrozadań, wiązania pętli, cena oferty, walidacja ilości oraz zakończenie i błędy operacji asynchronicznych.

Software EngineerFree

W zadaniu „co wypisze ten kod?” wyjaśnij kolejność liter oraz regułę, która ją ustala. Wyjaśnij, kiedy powstaje funkcja, do jakiego wiązania ma dostęp i kiedy trafia do kolejki. Potem popraw konkretny błąd oraz pokaż test, który odróżnia naprawę od przypadkowo poprawnego wyniku.

Prześledź mikrozadania, a potem sprawdź mały przykład koszyka: ilość przychodzi jako tekst, cena może się zmienić, a zapis pozycji jest asynchroniczny. Zacznij od Explain JavaScript Scope, Closures, Timers, and the Event Loop, odpowiadając na podstawie kolejnych zmian stanu.

Granica dowodów: dokumentacja MDN opisuje mechanizmy języka i przeglądarki; kod, kontrakt koszyka i testy poniżej są autorskie. Laboratorium uruchomiono w Chrome 154 jako zwykły skrypt strony. Nie sprawdzaliśmy Node.js, sieci, faktycznego zamówienia ani momentu rysowania ekranu. Nie przypisujemy tych zadań konkretnym pracodawcom na podstawie relacji kandydatów.

Trzy obszary zadania: kolejka, domknięcie i jawna interpretacja ilości

Najpierw ustal środowisko i oczekiwane zachowanie

Zanim przewidzisz wynik, zapytaj, gdzie działa program: w przeglądarce, w module czy w środowisku serwerowym. Nie przenoś automatycznie obserwacji dotyczącej kolejki timerów na każdą implementację JavaScript. W naszym laboratorium kod uruchamia się na lokalnej stronie, bez innych skryptów aplikacji i bez żądań HTTP do zewnętrznej usługi.

Kontrakt koszyka jest celowo mały. Ilość musi być liczbą całkowitą od 1 do 99. Dopuszczamy również tekst z taką liczbą i spacje na jego końcach; odrzucamy zapis wykładniczy, zera wiodące, wartości logiczne i pusty tekst. Cena zapamiętana dla oferty może różnić się od bieżącej ceny produktu. Są to decyzje ćwiczenia, nie uniwersalne reguły sklepu.

Dzięki temu możesz ocenić poprawkę według wymagania. Zamiana var na let nie rozstrzyga polityki ceny, a użycie Number nie określa dozwolonego formatu wejścia. Najpierw nazwij błąd: wspólne wiązanie, niejawna konwersja czy przedwczesne zakończenie operacji. Dopiero potem wybierz zmianę kodu.

Zadanie 1: mikrozadanie dodane przez mikrozadanie

Przewidź całą sekwencję, a następnie wskaż moment dodania każdego callbacku. Tablica trace zastępuje konsolę, aby wynik można było porównać dokładnie.

const trace = [];
trace.push('A');
queueMicrotask(() => trace.push('M1'));
Promise.resolve().then(() => {
  trace.push('P1');
  queueMicrotask(() => trace.push('M2'));
});
Promise.resolve().then(() => trace.push('P2'));
setTimeout(() => trace.push('T'), 0);
trace.push('B');

Wynik po wykonaniu timera to A, B, M1, P1, P2, M2, T. Najpierw wykonuje się kod synchroniczny. Następnie obsługiwane są już dodane mikrozadania. Kiedy działa P1, P2 czeka już w kolejce; nowe M2 trafia za nim. Timer nie przeskakuje przed opróżnienie tej kolejki tylko dlatego, że otrzymał opóźnienie zero.

Oficjalny przewodnik MDN po mikrozadaniach opisuje opróżnianie kolejki mikrozadań przed kolejnym zadaniem, również gdy pojawiają się nowe mikrozadania. Zastosowanie tej zasady do liter powyżej jest naszym wyprowadzeniem, potwierdzonym w laboratorium. Nie używaj skrótu „Promise zawsze wygrywa”: istotne są gotowość i moment zakolejkowania reakcji.

Jak zapisać ślad bez zgadywania czasu

Przygotuj trzy kolumny: wykonywany fragment, kolejka mikrozadań i oczekujące zadanie timera. Po dodaniu P2 kolejka zawiera M1, P1, P2. Po wykonaniu M1 zostają P1, P2. Podczas P1 dochodzi M2, więc kolejny stan to P2, M2. Notatka pokazuje, dlaczego P2 wyprzedza M2, choć M2 powstaje podczas P1.

Nie dopisuj do śladu „T po dokładnie zero milisekund”. Przykład sprawdza kolejność, a nie gwarantowany czas. Nie dowodzi również, że użytkownik zobaczy aktualizację DOM między konkretnymi wpisami. Zakończenie callbacku, opróżnienie mikrozadań i renderowanie strony to różne obserwacje; nasze testy rejestrują tylko pierwsze dwie przez kolejność wpisów.

Jeżeli zmienisz kod tak, aby P2 powstało dopiero wewnątrz innego callbacku, poprzednia tabela przestaje obowiązywać. Przelicz moment zakolejkowania zamiast zapamiętywać sekwencję. Podczas rozmowy dopowiedz, co zmieniło się w przesłankach. To pokazuje rozumienie mechanizmu i pozwala osobie oceniającej sprawdzić rozumowanie krok po kroku.

Dokładna kolejność A B M1 P1 P2 M2 T z M2 dodanym przez P1

Zadanie 2: dlaczego callbacki widzą trzy razy 3

W pierwszym wariancie wszystkie callbacki korzystają z jednego wiązania zmiennej i. Zanim zostaną wykonane, pętla zakończy się z wartością trzy.

const values = [];
for (var i = 0; i < 3; i++) {
  Promise.resolve().then(() => values.push(i));
}

Po wykonaniu reakcji wynik wynosi [3, 3, 3]. Zmiana deklaracji na let w nagłówku tej pętli daje [0, 1, 2], ponieważ callbacki otrzymują dostęp do wiązań właściwych poszczególnym iteracjom. Oba wyniki są zapisane w raporcie Chrome: var_binding i let_binding. Nie mów, że callback „kopiuje zmienną w chwili utworzenia”: ten opis nie tłumaczy pierwszego wariantu.

Dokumentacja MDN o domknięciach wiąże funkcję z jej otoczeniem leksykalnym. W przykładzie naprawiasz sposób utworzenia wiązań, nie kolejkę Promise. Zastąpienie reakcji timerami może zmienić moment wykonania, ale samo nie tworzy osobnych wartości i. Dodawanie opóźnienia jest więc nieuzasadnioną poprawką do tego błędu.

Sprawdź naprawę dla wszystkich trzech iteracji, a nie tylko pierwszej. Wynik [0] nie ujawni współdzielenia kolejnych wartości. Dodaj też krótkie wyjaśnienie, gdzie powstaje nowe wiązanie. Dzięki temu kod z let jest świadomą naprawą, a nie zapamiętaną odpowiedzią na popularną zagadkę.

Domknięcie ceny: aktualna wartość czy oferta

Teraz problem jest bardziej biznesowy. Produkt kosztuje dwadzieścia jednostek w chwili przygotowania oferty, a później dwadzieścia pięć. Którą cenę powinien zwrócić callback? JavaScript nie wybierze polityki za projektanta.

let price = 20;
const live = () => price;
const saved = price;
const snapshot = () => saved;
price = 25;

W laboratorium live() zwraca 25, a snapshot() zwraca 20. Pierwsza funkcja odczytuje bieżące wiązanie price; druga korzysta z osobnego wiązania zawierającego wcześniej zapisany prymityw. Oba zachowania mogą być poprawne dla różnych wymagań. Nie nazywaj automatycznie pierwszego „błędem domknięcia”, jeśli zamierzeniem jest aktualna cena.

Jeśli oferta ma zachować cenę, zapisz jej wartość oraz określ, kiedy oferta wygasa i kto ją weryfikuje. Tych dodatkowych reguł nie implementowaliśmy. Jeśli zamiast liczby zapiszesz odwołanie do mutowalnego obiektu produktu, sama deklaracja const saved = product nie zamrozi jego pól. To ważne ograniczenie proponowanej techniki; przykład potwierdza zachowanie liczbowego prymitywu.

Zadanie 3: tekstowa ilość i operator plus

Dla wejścia "2" wyrażenie raw + 1 daje tekst "21". Poprawnie zwalidowana liczba dwa plus jeden daje trzy. To nie drobny problem formatowania: aplikacja może wysłać inną ilość niż użytkownik zamierzał, choć oba wyniki dają się wyświetlić bez wyjątku.

Samo Number(raw) rozwiązuje tylko część sprawy. W laboratorium Number('') zwraca zero, a kontrakt odrzuca pusty tekst. Dokumentacja Number w MDN opisuje konwersję; to projektant musi zdecydować, które wejścia są dozwolone. Nie traktuj braku wyjątku podczas konwersji jako dowodu poprawności danych.

Dla naszego zakresu 1–99 walidator wygląda następująco:

function parseQty(raw) {
  if (typeof raw === 'number') {
    if (Number.isInteger(raw) && raw >= 1 && raw <= 99)
      return raw;
  } else if (typeof raw === 'string') {
    const text = raw.trim();
    if (/^[1-9]\d?$/.test(text)) return Number(text);
  }
  throw new TypeError('quantity');
}

Kolejność jest celowa: najpierw typ i dozwolony format, potem konwersja. Wyrażenie regularne dotyczy tylko tekstu i najwyżej dwóch cyfr; nie jest parserem wszystkich liczb JavaScript. Dzięki temu decyzje o zerach wiodących i zapisie wykładniczym pozostają widoczne. Inny kontrakt wymagałby innego walidatora oraz wyników oczekiwanych.

Macierz wejść, która sprawdza poprawkę

Zamiast ogólnego testu „nieprawidłowe dane”, zapisz konkretne wartości. Czternaście przypadków walidatora obejmuje oba końce zakresu i różnice typów. Odrzucenie oznacza TypeError; w raporcie testowym jest reprezentowane przez null, żeby można było porównać wynik w JSON.

WejścieWynik kontraktuCo sprawdza
1, 99Te same liczbyWłączone granice zakresu
"2", " 2 "2Dozwolony tekst i jawne obcięcie spacji
0, 100, 2.5OdrzucenieZakres i całkowitość
"02", "2e1", ""OdrzucenieFormat tekstu, nie tylko wartość po konwersji
null, true, NaN, InfinityOdrzucenieNiedozwolony typ albo wartość liczbowa

Ten zestaw nie sprawdza lokalizowanych separatorów, wielkich liczb ani ułamków walutowych, ponieważ kontrakt ich nie dopuszcza. Nie rozbudowuj parsera bez wymagania. W rozmowie nazwij granicę zastosowania: ilość produktu od jednej do dziewięćdziesięciu dziewięciu sztuk. Nie używaj tego walidatora do kwot: jego kontrakt dopuszcza tylko całkowitą ilość 1–99.

Zadanie 4: await nie naprawia forEach

Załóżmy, że zapis każdej pozycji czeka na timer reprezentujący pracę asynchroniczną. Ten skrót kończy etap wcześniej, niż sugeruje słowo await:

await [1, 2].forEach(async n => {
  await new Promise(resolve => setTimeout(resolve, 0));
  trace.push(n);
});
trace.push('END');

Po dokończeniu timerów zapisany ślad to END, 1, 2. Oficjalna dokumentacja forEach zaznacza, że metoda nie czeka na obietnice callbacków. Oczekujesz na wynik samego forEach, a nie na zbiór zadań utworzonych w jego wnętrzu. Dodatkowy await przed wywołaniem nie zmienia tego kontraktu.

Gdy wymaganiem jest przetwarzanie sekwencyjne, użyj for...of i oczekuj wewnątrz pętli. Sprawdzony wariant daje 1, 2, END. Gdy operacje są niezależne, można rozważyć zebranie obietnic i odpowiednią strategię współbieżności, ale to odrębna decyzja: znaczenie mają limit obciążenia, kolejność wyników i reakcja na błąd. Nie testowaliśmy tutaj kontrolera współbieżności.

Nie traktuj serializacji jako jedynej poprawnej naprawy każdego kodu asynchronicznego. W tym ćwiczeniu jest odpowiednia, bo chcemy zakończyć obie pozycje przed wpisem END i zachować kolejność. Zmiana wymagań na równoległy zapis wymaga również zmiany argumentacji oraz testów.

Co robi pusty catch z błędem

Ostatnia para testów dotyczy wyniku operacji. Przewodnik MDN o obietnicach opisuje propagację i obsługę błędów w łańcuchu. Promise.reject(new Error('save')).catch(() => {}) daje obietnicę spełnioną wartością undefined. Obsłużenie odrzucenia bez ponownego zgłoszenia lub jawnego wyniku błędu może ukryć nieudany zapis przed dalszym kodem.

Wariant catch(error => { throw error; }) zachowuje odrzucenie; test sprawdza komunikat save. Jeżeli potrzebujesz przechwycić błąd dla diagnostyki, ustal kontrakt dalszego wywołania: propagacja, kontrolowany wynik albo rzeczywista ścieżka odzyskiwania. Po pustym catch dalszy kod może uznać nieudany zapis za sukces; zniknięcie komunikatu nie potwierdza odzyskania.

W laboratorium nie wysłano żadnego zamówienia. Odrzucona obietnica jest kontrolowaną symulacją i zostaje obsłużona przez test. Nie sprawdzono ponowień, idempotencji ani częściowego sukcesu w usłudze zewnętrznej. Wymieniaj te kwestie jako następne wymagania, jeśli zadanie o nie pyta, a nie jako gotowe gwarancje krótkiego fragmentu kodu.

Jak obronić poprawkę przed kolejnym pytaniem

Po wskazaniu błędu zaproponuj najmniejszy eksperyment, który rozróżnia dwa wyjaśnienia. Dla ilości porównaj pusty tekst i tekst z cyfrą: oba przechodzą przez Number, ale tylko jeden spełnia wymaganie. Dla ceny zmień wartość po utworzeniu funkcji, a przed jej wywołaniem. Dla kolejki zapisz stan tuż przed P1 i tuż po nim. Każda próba sprawdza inną przesłankę, zamiast potwierdzać ogólne przekonanie, że kod „działa”.

Przy naprawie pętli zachowaj tę samą operację reprezentowaną przez timer. W obu sprawdzonych wariantach callback czeka na timer; zmienia się sposób oczekiwania na zakończenie pozycji. Dzięki temu różnicę END, 1, 2 i 1, 2, END można przypisać przepływowi sterowania, zamiast porównywać przypadkiem różne zadania. Nie potrzebujesz stoperów ani założenia o identycznym czasie wykonania.

Jeśli osoba prowadząca rozmowę doda odrzucenie jednej pozycji, najpierw określ oczekiwany wynik: przerwać, zebrać błędy czy kontynuować pozostałe? Nasza para testów pętli nie obejmuje tego wymagania. Dwa testy catch pokazują tylko lokalną zmianę odrzucenia w spełnienie albo jego propagację. Nie łącz tych dowodów w twierdzenie, że cały batch poprawnie obsługuje częściowy sukces. Możesz zaproponować taki następny test, wyraźnie oddzielając go od dwudziestu sześciu już wykonanych kontroli.

Co dokładnie potwierdza laboratorium

W Chrome 154 przeszło dwadzieścia sześć kontroli: jedna pełna sekwencja kolejki, dwa warianty wiązania pętli, dwie polityki odczytu ceny, czternaście wejść ilości, trzy wyniki konwersji i dodawania, dwa ślady zakończenia oraz dwa przypadki obsługi błędu. Lokalne pliki browser-lab.html, browser-lab.js i raport JSON pozwalają zestawić kod z wynikami; nie stanowią testu integracji sklepu.

Test porównuje ślady po zakończeniu zaplanowanej pracy; nie wyprowadza dokładnych czasów ze stoperów. Jeśli zmienisz kontekst wykonania, wejście lub sposób planowania callbacków, uruchom eksperyment ponownie i opisz nową granicę. Nie wystarczy przenieść starej odpowiedzi do podobnie wyglądającego fragmentu.

Pięć pytań do dalszej praktyki

Poniższe strony PracHub są dostępne; zachowujemy pełne angielskie tytuły. Dobór obejmuje przewidywanie wyniku i świadome rozszerzenia przypadku. Ostatnie dwa pytania rozwijają politykę pracy asynchronicznej, której nie implementuje laboratorium.

Pytanie PracHubCo przygotować
Explain JavaScript Scope, Closures, Timers, and the Event LoopŚlad kolejki i moment odczytu wiązania
Predict JavaScript let and var BehaviorKonkretny wynik i powód różnicy
Explain JavaScript Numeric Edge CasesKonwersję oddzieloną od kontraktu wejścia
Compare Promise.all, Promise.any, and Promise.allSettledZnaczenie częściowego sukcesu i odrzucenia
Run Promise Tasks with Bounded ConcurrencyWymagany limit i kryterium zakończenia

Wróć do Predict JavaScript let and var Behavior, zapisz przewidywany wynik i dopiero wtedy uruchom kod. Dobra poprawka ma wyraźny powód oraz test, który zawiedzie po przywróceniu błędu. To użyteczniejszy cel przygotowania niż zbiór odpowiedzi zapamiętanych bez środowiska i kontraktu.

Sources and Further Reading


Comments (0)