Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Ділення важкої задачі на частини». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Принцип чанкінгу простий: зробити невеликий шматок роботи (приблизно 1-2 ms), запланувати продовження через макрозадачу, щоб рушій встиг обробити UI, запити й таймери, і повторювати, доки робота не скінчиться.** Ключова деталь: `await Promise.resolve()` тут не допомагає, бо це мікрозадача й кадр не перемалюється. Потрібна саме макрозадача: `setTimeout(fn, 0)`, `MessageChannel`, `requestAnimationFrame`, `requestIdleCallback` у браузері або `setImmediate` у Node.js. Ділити можна за часовим бюджетом (працюємо N мілісекунд і віддаємо кадр) чи за кількістю ітерацій на зріз. Якщо обчислення справді важкі, краще взагалі винести їх у Web Worker або `worker_threads`. ```javascript function forEachChunked(arr, fn, { chunkSize = 10_000 } = {}) { return new Promise((resolve) => { let i = 0; (function loop() { const end = Math.min(i + chunkSize, arr.length); for (; i < end; i++) fn(arr[i], i); i < arr.length ? setTimeout(loop, 0) : resolve(); })(); }); } ``` **Ключове:** маленький шматок роботи плюс макрозадача на продовження; мікрозадачі не віддають кадр, а справді важке краще винести у воркер.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Чанкінг, це техніка, коли важке обчислення ріжуть на короткі шматки й між ними віддають керування event loop через макрозадачу.** Завдяки цьому браузер встигає перемалювати кадр і обробити ввід, а сервер Node.js, обслужити інші запити. ## Теорія ### TL;DR - Робимо невеликий шматок роботи, приблизно 1-2 ms. - Плануємо продовження через макрозадачу, щоб браузер або Node встигли обробити UI, запити й таймери. - Повторюємо, доки не закінчимо. - `await Promise.resolve()` не допомагає: це мікрозадача, вона не віддає кадр. - Макрозадачі: `setTimeout(fn, 0)`, `setImmediate`, `MessageChannel`, `requestAnimationFrame`, `requestIdleCallback`. - Дуже важку математику чи парсинг краще винести у Web Worker або `worker_threads`, а не ділити руками. ### Швидкий приклад ```javascript // Chunking by operation count: simple and reliable function forEachChunked(arr, fn, { chunkSize = 10_000, schedule = setTimeout } = {}) { return new Promise((resolve) => { let i = 0; (function loop() { const end = Math.min(i + chunkSize, arr.length); for (; i < end; i++) fn(arr[i], i); i < arr.length ? schedule(loop, 0) : resolve(); })(); }); } ``` ### Принцип - Робимо невеликий шматок роботи (наприклад, 1-2 ms). - Плануємо продовження через макрозадачу (щоб браузер або Node змогли обробити UI, запити, таймери). - Повторюємо, доки не закінчимо. > Важливо: `await Promise.resolve()` не допомагає, це мікрозадача, вона не віддає кадр і UI. Потрібна макрозадача: `setTimeout(0)`, `setImmediate`, `MessageChannel`, `requestAnimationFrame`, `requestIdleCallback`. ### Універсальна утиліта чанкінгу #### У браузері (дружня до UI, 60fps) ```javascript // Schedulers for different modes: const scheduleTimeout = (fn) => setTimeout(fn, 0); // simple const scheduleRAF = (fn) => requestAnimationFrame(fn); // gentle on the UI const scheduleIdle = (fn) => requestIdleCallback(fn, { timeout: 50 }); // when the browser is idle (a fallback is mandatory) // Time based chunker: works for about N milliseconds, then yields the loop export function runSliced(work, { budgetMs = 4, schedule = scheduleRAF } = {}) { return new Promise((resolve, reject) => { try { const step = (deadlineLike) => { const start = performance.now(); while (work.hasMore()) { work.doOne(); const now = performance.now(); // Over budget, so yield the frame if (now - start >= budgetMs) { schedule(step); return; } } resolve(); }; schedule(step); } catch (e) { reject(e); } }); } // Usage example: processing a large array export async function processBigArray(items) { let i = 0; const work = { hasMore: () => i < items.length, doOne: () => { const x = items[i]; // ...heavy work on x... i++; } }; await runSliced(work, { budgetMs: 3, schedule: scheduleRAF }); } ``` #### Node.js (сервер не «висне») ```javascript const scheduleImmediate = (fn) => setImmediate(fn); function runSlicedNode(work, { iterationsPerSlice = 50_000, schedule = scheduleImmediate } = {}) { return new Promise((resolve, reject) => { try { const step = () => { let n = iterationsPerSlice; while (work.hasMore() && n--) { work.doOne(); } work.hasMore() ? schedule(step) : resolve(); }; schedule(step); } catch (e) { reject(e); } }); } // Example: async function crunch() { let i = 0, N = 1e9, sum = 0; const work = { hasMore: () => i < N, doOne: () => { sum += i % 7; i++; } }; await runSlicedNode(work, { iterationsPerSlice: 100_000 }); return sum; } ``` ### Швидкі рецепти #### 1) Чанкінг за кількістю операцій (просто й надійно) ```javascript function forEachChunked(arr, fn, { chunkSize = 10_000, schedule = setTimeout } = {}) { return new Promise((resolve) => { let i = 0; (function loop() { const end = Math.min(i + chunkSize, arr.length); for (; i < end; i++) fn(arr[i], i); i < arr.length ? schedule(loop, 0) : resolve(); })(); }); } ``` #### 2) Через `requestAnimationFrame` (плавний UI) ```javascript function timeSliced(fn, { budgetMs = 4 } = {}) { return new Promise((resolve) => { function tick() { const start = performance.now(); let more = true; while (more && performance.now() - start < budgetMs) { more = fn(); // fn() does a little work and returns true if something is left } more ? requestAnimationFrame(tick) : resolve(); } requestAnimationFrame(tick); }); } ``` #### 3) Максимальна обережність, `requestIdleCallback` (якщо доступний) ```javascript function idleSliced(fn, timeout = 50) { return new Promise((resolve) => { function step(deadline) { let more = true; while (more && (deadline.timeRemaining() > 1)) { more = fn(); } more ? requestIdleCallback(step, { timeout }) : resolve(); } requestIdleCallback(step, { timeout }); }); } ``` #### 4) Найшвидший планувальник макрозадач, `MessageChannel` ```javascript function macroTaskScheduler() { const ch = new MessageChannel(); let queue = []; ch.port1.onmessage = () => queue.shift()?.(); return (fn) => { queue.push(fn); ch.port2.postMessage(0); }; } const scheduleFast = macroTaskScheduler(); ``` ### Що обрати - **Анімації та інтерактивний UI:** `requestAnimationFrame` з бюджетом 2-5 ms. - **Низький пріоритет, фонові задачі:** `requestIdleCallback` (і fallback на `setTimeout`). - **Просто і всюди:** `setTimeout(0)` або `MessageChannel`. - **Node.js:** `setImmediate` або розбивка за кількістю ітерацій. - **Дуже важка математика чи парсинг:** краще винести у Web Worker (браузер) або `worker_threads` (Node), щоб не ділити вручну. ### Типові помилки Антипатерни, яких варто уникати: - Нескінченні або довгі синхронні цикли без «поступки» (`while (true) {}` чи `for` на мільярди ітерацій). - Надія на `await Promise.resolve()` для розвантаження UI, це мікрозадача, кадр не перемалюється. - Синхронні версії вводу-виводу й криптографії в Node.js (`readFileSync`, `pbkdf2Sync`), вони блокують увесь процес. - Занадто малий чанк: якщо на кожен елемент планувати окрему макрозадачу, накладні витрати планувальника з'їдять увесь виграш, а робота розтягнеться на хвилини. - `requestIdleCallback` без `timeout` і без fallback: на завантаженій сторінці задача може не запуститися взагалі, а в частині рушіїв API просто відсутній. - Читання розмірів або стилів DOM усередині чанка: це змушує браузер робити примусовий reflow і зводить нанівець економію бюджету кадру.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.