JavaScript: Junior
20 питань · ~15 хв · Версія v3.0
Увійдіть, щоб продовжити
Базові знання JavaScript: типи, область видимості, функції, масиви, асинхронність.
- За спробу
- 20
- У пулі
- 100
- Проходжень
- 0
- Середній бал
- -
- Пройшли на 70%+
- -
Питання для підготовки
37 питаньPromise - об'єкт-обіцянка результату, який з'явиться пізніше: відповідь сервера, прочитаний файл, таймер. Замість того щоб передавати колбек, функція одразу повертає Promise, а результат на нього «підписують».
Три стани:
- pending - очікування, результату ще немає;
- fulfilled - виконано успішно, є значення;
- rejected - відхилено, є причина (зазвичай об'єкт
Error).
Перехід з pending відбувається один раз і назавжди: виконаний Promise не стане відхиленим.
fetch('/api/user')
.then((response) => response.json())
.then((user) => console.log(user.name))
.catch((error) => console.error('Не вдалося завантажити', error))
.finally(() => hideSpinner());
Що варто знати:
then()повертає новий Promise, тому виклики можна з'єднувати в ланцюжок. Значення, повернене зthen, стає результатом наступного кроку.- Один
catch()наприкінці ловить помилку з будь-якого попереднього кроку. - Promise без обробника помилки, який відхилився, дає
unhandledrejection- у Node.js це за замовчуванням завершує процес. fetchвідхиляє Promise лише при мережевій помилці. Відповідь 404 чи 500 - це виконаний Promise зresponse.ok === false.
По суті нічим: async/await - зручніший запис тих самих Promise. Асинхронний код читається зверху вниз, як синхронний.
// then()
function loadUser(id) {
return fetch(`/api/users/${id}`)
.then((r) => r.json())
.then((user) => user.name);
}
// async/await
async function loadUser(id) {
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user.name;
}
Правила:
async-функція завжди повертає Promise.return 5у ній - цеPromise, що виконається з 5.awaitчекає Promise і повертає його значення. Якщо Promise відхилено,awaitкидає виняток.- Помилки ловлять звичайним
try/catch.
try {
const user = await loadUser(1);
} catch (error) {
showError(error.message);
}
Пастка - зайва послідовність:
const user = await loadUser(); // чекаємо
const posts = await loadPosts(); // і лише тоді починаємо друге
Якщо запити незалежні, їх запускають разом: const [user, posts] = await Promise.all([loadUser(), loadPosts()]);.
await на верхньому рівні (поза функцією) працює в ES-модулях.
setTimeout(fn, 0) означає не «виконай зараз», а «постав у чергу, щойно буде можливість». JavaScript у браузері виконується в одному потоці: поки працює поточний код, ніщо інше не запуститься.
console.log('1');
setTimeout(() => console.log('3'), 0);
Promise.resolve().then(() => console.log('2б'));
console.log('2');
// 1, 2, 2б, 3
Порядок такий:
- виконується весь синхронний код до кінця;
- виконуються всі мікрозадачі (колбеки Promise,
queueMicrotask); - браузер може перемалювати сторінку;
- береться наступна макрозадача - зокрема колбек таймера.
Тому колбек Promise спрацьовує раніше за таймер з нульовою затримкою.
Затримка - мінімальна, а не точна. setTimeout(fn, 100) гарантує лише, що колбек запуститься не раніше ніж за 100 мс. Якщо головний потік зайнятий важким обчисленням 2 секунди, таймер спрацює через 2 секунди.
Інші обмеження:
- вкладені таймери після п'яти рівнів отримують мінімальну затримку 4 мс;
- у фонових вкладках браузер пригальмовує таймери до разу на секунду і рідше - не можна покладатися на них для точного відліку часу;
- для анімацій є
requestAnimationFrame, синхронізований з перемальовуванням екрана.
Навіщо setTimeout(fn, 0) на практиці: відкласти роботу до моменту, коли поточна подія оброблена й DOM оновлено, або розбити довгу роботу на шматки, даючи браузеру перемальовувати сторінку між ними.
Колбек (функція зворотного виклику) - функція, яку передають іншій функції, щоб та викликала її пізніше:
button.addEventListener('click', () => console.log('клік'));
[1, 2, 3].map((n) => n * 2); // теж колбек, але синхронний
До появи Promise асинхронний код писали лише колбеками: «зроби запит і, коли буде відповідь, виклич цю функцію».
Пекло колбеків з'являється, коли кроки залежать один від одного:
getUser(id, (error, user) => {
if (error) return showError(error);
getOrders(user.id, (error, orders) => {
if (error) return showError(error);
getInvoice(orders[0].id, (error, invoice) => {
if (error) return showError(error);
render(invoice);
});
});
});
Проблеми:
- код росте вправо і погано читається;
- помилку треба перевіряти на кожному рівні окремо - легко пропустити;
- немає простого способу запустити кілька операцій паралельно й дочекатися всіх;
- функція може викликати колбек двічі чи жодного разу - і нічого вас від цього не захистить.
Те саме з Promise і async/await:
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
render(await getInvoice(orders[0].id));
} catch (error) {
showError(error);
}
Код читається зверху вниз, одна обробка помилок на всі кроки, а Promise гарантовано виконується чи відхиляється рівно один раз.
Колбеки нікуди не зникли: обробники подій, методи масивів, setTimeout - це колбеки, і для них вони доречні. Від колбеків відмовилися саме для одноразових асинхронних результатів.
Усі обходять масив і викликають функцію для кожного елемента:
map- новий масив тієї ж довжини з перетвореними елементами.filter- новий масив лише з тими елементами, для яких функція повернула істинне значення.reduce- згортає масив в одне значення: суму, об'єкт, інший масив.forEach- нічого не повертає (undefined), лише виконує дію для кожного елемента.
const orders = [{ total: 100, paid: true }, { total: 250, paid: false }, { total: 40, paid: true }];
orders.map((o) => o.total); // [100, 250, 40]
orders.filter((o) => o.paid); // два оплачені
orders.reduce((sum, o) => sum + o.total, 0); // 390
orders.forEach((o) => console.log(o.total)); // лише побічна дія
Типові помилки:
mapзаради побічної дії, без використання результату. Для цього єforEachчиfor...of.reduceбез початкового значення на порожньому масиві кидаєTypeError.- Перервати
forEachнеможливо (breakне працює). Якщо треба зупинитися, -for...of,some()чиfind().
Усі чотири не змінюють початковий масив (хіба що сам колбек зробить це навмисно), тож їх зручно з'єднувати в ланцюжок: orders.filter(...).map(...).
Спершу почитати
Прочитати - ще не значить знати
20 питань, по одному на екран, ~15 хв. Після завершення - розбір кожної помилки з посиланням на питання.