Якою б позицією ми не володіли в процесі розробки ПЗ, нам варто подбати про зручність «героя», для якого ми створюємо цей продукт. Подбати про користувача. Навіть ідеально відмальований дизайн не врятує вашу систему, якщо інтерфейс буде «зависати», або користувач не розумітиме, що відбувається із системою. Ще складнішою буде ситуація, якщо мова йде про застосунок в архітектурі клієнт-сервер. У такому випадку доведеться продумати процеси як на фронтенді, так і на бекенді.
Я бачу вирішення цієї задачі у виділенні навантаженої частини коду (наприклад, обчислення або обробка великого обсягу даних) в окремі сервіси. Причому контроль над сервісом має залишатися у користувача. Сам сервіс потрібно написати так, щоб він взаємодіяв із зовнішнім світом.
Для початку серії статей про мій підхід до таких рішень я хотів би звернути увагу на деякі особливості мови JavaScript, але з часом опишу кейси і для інших технологій.
Пильний погляд у моєму прикладі з нейронною мережею помітить, що під час розрахунку ваг інтерфейс підвисає на кілька секунд. Причина банальна. Алгоритм виконує велику кількість ітерацій, і поки цикл не завершиться — інтерпретатор просто не зможе обробляти інші операції.
Щоб продемонструвати цей ефект, я написав невеликий приклад.
- https://github.com/ninydev-com/background-job-example
- https://ninydev-com.github.io/background-job-example
Як бачите, при виконанні циклу інтерфейс просто зависає, поки інтерпретатор не завершить всі ітерації.
while (Date.now() < endTime) {
const randomNumber = Math.random()
console.log(Date.now())
}
async function cycle() {
const endTime = Date.now() + cycleTime
while (Date.now() < endTime) {
const randomNumber = Math.random()
console.log(Date.now())
}
}
await cycle()
Інтерпретатор JavaScript не дозволяє виконувати інші події чи операції, доки задача не завершиться повністю. І це не змінюється навіть тоді, коли ви використовуєте проміс із операцією await.
Якщо розробник інтерфейсу задумається про те, щоб хоча б ненадовго відпускати інтерпретатор для інших завдань, то інтерфейс перестане зависати. Подивіться на цей приклад — кожну ітерацію ми обгорнули в окреме завдання, що дозволяє обробляти інші задачі з обох черг.
function doTimeOut(endTime: number) {
const randomNumber = Math.random()
if (Date.now() < endTime) {
setTimeout(() => doTimeOut(endTime), 1)
} else {
toast.success('End')
}
}
const startTimeOut = () => {
const endTime = Date.now() + cycleTime
toast.info('Start timeout')
setTimeout(() => doTimeOut(endTime), 1)
}
setTimeout(..., 1)
: Використання затримки в 1 мілісекунду працює, але можна використовуватиrequestAnimationFrame
для ще більш оптимального управління потоками на рівні браузера, якщо це підходить для твоєї задачі.
Використання промісів і правильне розуміння структури двигуна JavaScript дозволяють вирішити проблеми із зависанням інтерфейсу. Завдяки асинхронному виконанню задач ми можемо зберегти чуйність інтерфейсу та забезпечити комфорт користувачів. Важливо не лише оптимізувати алгоритми, але й враховувати архітектурні особливості мови, щоб створювати ефективні й надійні рішення.