logo

Пекло зворотного виклику в JavaScript

JavaScript — це асинхронна (неблокуюча) і однопотокова мова програмування, тобто одночасно може виконуватися лише один процес.

У мовах програмування пекло зворотного виклику зазвичай відноситься до неефективного способу написання коду за допомогою асинхронних викликів. Вона також відома як Піраміда Долі.

Пекло зворотного виклику в JavaScript називається ситуацією, коли виконується надмірна кількість вкладених функцій зворотного виклику. Це зменшує читабельність коду та обслуговування. Ситуація пекла зворотного виклику зазвичай виникає під час роботи з асинхронними операціями запитів, такими як створення кількох запитів API або обробка подій зі складними залежностями.

Щоб краще зрозуміти пекло зворотних викликів у JavaScript, спочатку зрозумійте зворотні виклики та цикли подій у JavaScript.

Зворотні виклики в JavaScript

JavaScript розглядає все як об’єкт, наприклад рядки, масиви та функції. Отже, концепція зворотного виклику дозволяє нам передати функцію як аргумент іншій функції. Функція зворотного виклику завершить виконання спочатку, а батьківська функція буде виконана пізніше.

Функції зворотного виклику виконуються асинхронно і дозволяють коду продовжувати працювати, не чекаючи завершення асинхронного завдання. Коли кілька асинхронних завдань поєднуються, і кожне завдання залежить від свого попереднього завдання, структура коду стає складною.

інакше, якщо java

Давайте розберемося з використанням і важливістю зворотних викликів. Припустімо, наприклад, у нас є функція, яка приймає три параметри, один рядок і два числа. Нам потрібен вихід на основі тексту рядка з кількома умовами.

Розглянемо наведений нижче приклад:

 function expectedResult(action, x, y){ if(action === 'add'){ return x+y }else if(action === 'subtract'){ return x-y } } console.log(expectedResult('add',20,10)) console.log(expectedResult('subtract',30,10)) 

Вихід:

 30 20 

Наведений вище код працюватиме добре, але нам потрібно додати більше завдань, щоб зробити код масштабованим. Кількість умовних операторів також буде збільшуватися, що призведе до безладної структури коду, який потрібно оптимізувати та читати.

Отже, ми можемо переписати код кращим чином наступним чином:

 function add(x,y){ return x+y } function subtract(x,y){ return x-y } function expectedResult(callBack, x, y){ return callBack(x,y) } console.log(expectedResult(add, 20, 10)) console.log(expectedResult(subtract, 30, 10)) 

Вихід:

 30 20 

Все одно вихід буде той же. Але в наведеному вище прикладі ми визначили його окреме тіло функції та передали функцію як функцію зворотного виклику функції очікуваного результату. Отже, якщо ми хочемо розширити функціональність очікуваних результатів, щоб ми могли створити інше функціонуюче тіло з іншою операцією та використовувати його як функцію зворотного виклику, це полегшить розуміння та покращить читабельність коду.

Існують інші різні приклади зворотних викликів, доступних у підтримуваних функціях JavaScript. Декілька поширених прикладів – це слухачі подій і функції масиву, такі як map, reduce, filter тощо.

Щоб краще зрозуміти це, ми повинні зрозуміти передачу за значенням і передачу за посиланням JavaScript.

JavaScript підтримує два типи типів даних: примітивні та непримітивні. Примітивні типи даних — це undefined, null, string і boolean, які не можна змінити, або ми можемо сказати незмінні порівняно; непримітивні типи даних - це масиви, функції та об'єкти, які можна змінювати або змінювати.

Передача за посиланням передає посилальну адресу об’єкта, як функцію можна прийняти як аргумент. Отже, якщо значення в цій функції змінено, це призведе до зміни вихідного значення, яке доступне за межами функції.

Для порівняння, концепція передачі за значенням не змінює вихідне значення, яке доступне поза тілом функції. Замість цього він скопіює значення в два різні місця, використовуючи їхню пам’ять. JavaScript ідентифікував усі об’єкти за їх посиланням.

У JavaScript addEventListener прослуховує такі події, як клацання, наведення миші та відхід миші, і приймає другий аргумент як функцію, яка буде виконана після запуску події. Ця функція використовується концепцією передачі за посиланням і передається без дужок.

Розглянемо наведений нижче приклад; у цьому прикладі ми передали функцію greet як аргумент у addEventListener як функцію зворотного виклику. Він буде викликаний, коли ініціюється подія клацання:

Test.html:

як перевірити розмір екрана
 Javascript Callback Example <h3>Javascript Callback</h3> Click Here to Console const button = document.getElementById(&apos;btn&apos;); const greet=()=&gt;{ console.log(&apos;Hello, How are you?&apos;) } button.addEventListener(&apos;click&apos;, greet) 

Вихід:

Пекло зворотного виклику в JavaScript

У наведеному вище прикладі ми передали функцію greet як аргумент у addEventListener як функцію зворотного виклику. Він буде викликаний, коли ініціюється подія клацання.

Так само фільтр також є прикладом функції зворотного виклику. Якщо ми використовуємо фільтр для ітерації масиву, він прийматиме іншу функцію зворотного виклику як аргумент для обробки даних масиву. Розглянемо приклад нижче; у цьому прикладі ми використовуємо функцію larger для друку числа, більшого за 5 у масиві. Ми використовуємо функцію isGreater як функцію зворотного виклику в методі фільтра.

 const arr = [3,10,6,7] const isGreater = num =&gt; num &gt; 5 console.log(arr.filter(isGreater)) 

Вихід:

 [ 10, 6, 7 ] 

Наведений вище приклад показує, що функція larger використовується як функція зворотного виклику в методі filter.

Щоб краще зрозуміти зворотні виклики та цикли подій у JavaScript, давайте обговоримо синхронний і асинхронний JavaScript:

Синхронний JavaScript

Давайте розберемося, які особливості має мова синхронного програмування. Синхронне програмування має такі особливості:

Блокування виконання: Синхронна мова програмування підтримує техніку блокування виконання, що означає, що вона блокує виконання наступних операторів, які будуть виконані. Таким чином, досягається передбачуване та детерміноване виконання операторів.

Послідовний потік: Синхронне програмування підтримує послідовний потік виконання, що означає, що кожен оператор виконується послідовно, як один за іншим. Мовна програма очікує завершення оператора, перш ніж перейти до наступного.

Простота: Часто синхронне програмування вважається легким для розуміння, оскільки ми можемо передбачити його порядок виконання. Як правило, це лінійно і легко передбачити. Невеликі програми добре розробляти на цих мовах, оскільки вони можуть обробляти критичний порядок операцій.

Пряма обробка помилок: У мові синхронного програмування обробка помилок дуже проста. Якщо під час виконання оператора виникає помилка, вона видасть помилку, і програма зможе її перехопити.

У двох словах, синхронне програмування має дві основні функції, тобто одне завдання виконується за раз, а наступний набір наступних завдань буде розглянуто лише після завершення поточного завдання. Таким чином, це слідує за послідовним виконанням коду.

Така поведінка програмування, коли виконується оператор, мова створює ситуацію блокового коду, оскільки кожне завдання має чекати завершення попереднього завдання.

Але коли люди говорять про JavaScript, завжди було незрозуміло, чи є він синхронним чи асинхронним.

У наведених вище прикладах, коли ми використовували функцію як зворотний виклик у функції фільтра, вона виконувалася синхронно. Тому це називається синхронним виконанням. Функція фільтра повинна чекати, поки функція більшого рівня завершить своє виконання.

Отже, функція зворотного виклику також називається блокуванням зворотних викликів, оскільки вона блокує виконання батьківської функції, у якій її було викликано.

Насамперед, JavaScript вважається однопоточним синхронним і блокуючим за своєю природою. Але використовуючи кілька підходів, ми можемо змусити його працювати асинхронно на основі різних сценаріїв.

Тепер давайте розберемося з асинхронним JavaScript.

Асинхронний JavaScript

Асинхронна мова програмування спрямована на підвищення продуктивності програми. У таких сценаріях можна використовувати зворотні виклики. Ми можемо проаналізувати асинхронну поведінку JavaScript на прикладі нижче:

 function greet(){ console.log(&apos;greet after 1 second&apos;) } setTimeout(greet, 1000) 

З наведеного вище прикладу функція setTimeout приймає зворотний виклик і час у мілісекундах як аргументи. Зворотний виклик викликається після зазначеного часу (тут 1 с). Коротше кажучи, функція чекатиме на виконання 1 с. Тепер подивіться на наведений нижче код:

 function greet(){ console.log(&apos;greet after 1 second&apos;) } setTimeout(greet, 1000) console.log(&apos;first&apos;) console.log(&apos;Second&apos;) 

Вихід:

 first Second greet after 1 second 

З наведеного вище коду, повідомлення журналу після setTimeout будуть виконані першими, поки таймер пройде. Таким чином, це одна секунда, а потім вітальне повідомлення через 1 секундний інтервал часу.

У JavaScript setTimeout є асинхронною функцією. Кожного разу, коли ми викликаємо функцію setTimeout, вона реєструє функцію зворотного виклику (у цьому випадку greet), яка буде виконана після вказаної затримки. Однак це не блокує виконання наступного коду.

У наведеному вище прикладі повідомлення журналу є синхронними операторами, які виконуються негайно. Вони не залежать від функції setTimeout. Таким чином, вони виконують і записують відповідні повідомлення на консоль, не чекаючи затримки, зазначеної в setTimeout.

Тим часом цикл подій у JavaScript обробляє асинхронні завдання. У цьому випадку він чекає, поки мине задана затримка (1 секунда), і після закінчення цього часу підбирає функцію зворотного виклику (greet) і виконує її.

Таким чином, інший код після функції setTimeout виконувався під час роботи у фоновому режимі. Така поведінка дозволяє JavaScript виконувати інші завдання в очікуванні завершення асинхронної операції.

Нам потрібно зрозуміти стек викликів і чергу зворотних викликів, щоб обробляти асинхронні події в JavaScript.

Розгляньте зображення нижче:

Пекло зворотного виклику в JavaScript

З наведеного вище зображення типовий двигун JavaScript складається з купи пам’яті та стека викликів. Стек викликів виконує весь код без очікування, коли надсилається до стеку.

Пам'ять купи відповідає за виділення пам'яті для об'єктів і функцій під час виконання, коли вони потрібні.

Тепер механізми нашого браузера складаються з кількох веб-API, таких як DOM, setTimeout, console, fetch тощо, і механізм може отримати доступ до цих API за допомогою глобального об’єкта вікна. На наступному кроці деякі цикли подій відіграють роль воротаря, який вибирає запити функцій у черзі зворотного виклику та надсилає їх у стек. Ці функції, такі як setTimeout, потребують певного часу очікування.

Тепер повернемося до нашого прикладу, функції setTimeout; коли функція зустрічається, таймер реєструється в черзі зворотного виклику. Після цього залишок коду надсилається в стек викликів і виконується, коли функція досягає ліміту таймера, термін його дії закінчується, і черга зворотного виклику надсилає функцію зворотного виклику, яка має вказану логіку та зареєстрована у функції тайм-ауту . Таким чином, він буде виконаний через зазначений час.

Пекельні сценарії зворотного виклику

Тепер ми обговорили зворотні виклики, синхронний, асинхронний та інші відповідні теми для пекла зворотних викликів. Давайте розберемося, що таке пекло зворотного виклику в JavaScript.

Ситуація, коли кілька зворотних викликів вкладено, відома як пекло зворотного виклику, оскільки його форма коду виглядає як піраміда, яку також називають «пірамідою загибелі».

шаблони проектування в java

Пекло зворотного виклику ускладнює розуміння та підтримку коду. Здебільшого ми можемо спостерігати цю ситуацію під час роботи у вузлі JS. Наприклад, розглянемо наведений нижче приклад:

 getArticlesData(20, (articles) =&gt; { console.log(&apos;article lists&apos;, articles); getUserData(article.username, (name) =&gt; { console.log(name); getAddress(name, (item) =&gt; { console.log(item); //This goes on and on... } }) 

У наведеному вище прикладі getUserData отримує ім’я користувача, яке залежить від списку статей, або потребує вилучення відповіді getArticles, яка знаходиться всередині статті. getAddress також має подібну залежність, яка залежить від відповіді getUserData. Ця ситуація називається пеклом зворотного виклику.

Внутрішню роботу пекла зворотного виклику можна зрозуміти на прикладі нижче:

Давайте зрозуміємо, що нам потрібно виконати завдання A. Щоб виконати завдання, нам потрібні деякі дані із завдання B. Аналогічно; у нас є різні завдання, які залежать одне від одного і виконуються асинхронно. Таким чином, він створює ряд функцій зворотного виклику.

Давайте розберемося з Promises у JavaScript і як вони створюють асинхронні операції, що дозволяє нам уникнути написання вкладених зворотних викликів.

JavaScript обіцяє

У JavaScript проміси були представлені в ES6. Це об’єкт із синтаксичним покриттям. Завдяки своїй асинхронній поведінці це альтернативний спосіб уникнути запису зворотних викликів для асинхронних операцій. Зараз такі веб-інтерфейси API, як fetch(), реалізуються з використанням багатообіцяючого, який забезпечує ефективний спосіб доступу до даних із сервера. Це також покращило читабельність коду та є способом уникнути написання вкладених зворотних викликів.

Обіцянки в реальному житті виражають довіру між двома або більше особами та впевненість, що певна річ обов’язково станеться. У JavaScript Promise — це об’єкт, який забезпечує створення єдиного значення в майбутньому (за потреби). Promise у JavaScript використовується для керування та вирішення асинхронних операцій.

Promise повертає об’єкт, який забезпечує та представляє завершення або збій асинхронних операцій та його вихід. Це проксі для значення, не знаючи точного результату. Для асинхронних дій корисно надати кінцеве значення успіху або причину невдачі. Таким чином, асинхронні методи повертають значення, як і синхронний метод.

Зазвичай обіцянки мають такі три стани:

  • Виконано: стан виконано, коли застосовану дію було вирішено або успішно завершено.
  • Очікує: стан «Очікує», коли запит обробляється, а застосовану дію не було ані вирішено, ані відхилено та все ще перебуває у своєму початковому стані.
  • Відхилено: відхилений стан – це коли застосовану дію було відхилено, що спричиняє збій бажаної операції. Причиною відхилення може бути що завгодно, включно з несправністю сервера.

Синтаксис обіцянок:

 let newPromise = new Promise(function(resolve, reject) { // asynchronous call is made //Resolve or reject the data }); 

Нижче наведено приклад написання обіцянок:

Це приклад написання обіцянки.

 function getArticleData(id) { return new Promise((resolve, reject) =&gt; { setTimeout(() =&gt; { console.log(&apos;Fetching data....&apos;); resolve({ id: id, name: &apos;derik&apos; }); }, 5000); }); } getArticleData(&apos;10&apos;).then(res=&gt; console.log(res)) 

У наведеному вище прикладі ми можемо побачити, як ми можемо ефективно використовувати обіцянки, щоб зробити запит із сервера. Ми можемо помітити, що читабельність коду покращена у наведеному вище коді, ніж у зворотних викликах. Promises надають такі методи, як .then() і .catch(), які дозволяють нам обробляти статус операції в разі успіху чи невдачі. Ми можемо вказати випадки для різних станів обіцянок.

Async/Await у JavaScript

Це ще один спосіб уникнути використання вкладених зворотних викликів. Async/Await дозволяє нам використовувати обіцянки набагато ефективніше. Ми можемо уникнути використання ланцюжка методів .then() або .catch(). Ці методи також залежать від функцій зворотного виклику.

Async/Await можна точно використовувати з Promise для покращення продуктивності програми. Це внутрішньо вирішило обіцянки та забезпечило результат. Крім того, знову ж таки, він більш читабельний, ніж методи () або catch().

алфавіт пронумерований

Ми не можемо використовувати Async/Await зі звичайними функціями зворотного виклику. Щоб використовувати його, ми повинні зробити функцію асинхронною, написавши ключове слово async перед ключовим словом function. Однак внутрішньо він також використовує ланцюжок.

Нижче наведено приклад Async/Await:

 async function displayData() { try { const articleData = await getArticle(10); const placeData = await getPlaces(article.name); const cityData = await getCity(place) console.log(city); } catch (err) { console.log(&apos;Error: &apos;, err.message); } } displayData(); 

Щоб використовувати Async/Await, у функції має бути вказано ключове слово async, а ключове слово await має бути записано всередині функції. Async зупинить своє виконання, доки Promise не буде вирішено або відхилено. Його буде відновлено, коли буде роздано Обіцянку. Після вирішення значення виразу очікування буде збережено в змінній, яка його містить.

Резюме:

У двох словах, ми можемо уникнути вкладених зворотних викликів, використовуючи обіцянки та async/await. Окрім цього, ми можемо застосувати інші підходи, такі як написання коментарів, і розділення коду на окремі компоненти також може бути корисним. Але в наш час розробники віддають перевагу використанню async/await.

висновок:

Пекло зворотного виклику в JavaScript називається ситуацією, коли виконується надмірна кількість вкладених функцій зворотного виклику. Це зменшує читабельність коду та обслуговування. Ситуація пекла зворотного виклику зазвичай виникає під час роботи з асинхронними операціями запитів, такими як створення кількох запитів API або обробка подій зі складними залежностями.

Щоб краще зрозуміти пекло зворотного виклику в JavaScript.

JavaScript розглядає все як об’єкт, наприклад рядки, масиви та функції. Отже, концепція зворотного виклику дозволяє нам передати функцію як аргумент іншій функції. Функція зворотного виклику завершить виконання спочатку, а батьківська функція буде виконана пізніше.

Функції зворотного виклику виконуються асинхронно і дозволяють коду продовжувати працювати, не чекаючи завершення асинхронного завдання.