JavaScript е асинхронен (неблокиращ) и еднонишков език за програмиране, което означава, че само един процес може да се изпълнява в даден момент.
В езиците за програмиране адът на обратното извикване обикновено се отнася до неефективен начин за писане на код с асинхронни извиквания. Известна е още като Пирамидата на гибелта.
Адът на обратното извикване в JavaScript се нарича ситуация, при която се изпълнява прекомерно количество вложени функции за обратно извикване. Това намалява четливостта на кода и поддръжката. Ситуацията на ада с обратно извикване обикновено възниква, когато се работи с асинхронни операции на заявки, като например правене на множество API заявки или обработка на събития със сложни зависимости.
бързо сортиране
За да разберете по-добре ада на обратното извикване в JavaScript, първо разберете обратните извиквания и циклите на събития в JavaScript.
Обратни извиквания в JavaScript
JavaScript разглежда всичко като обект, като низове, масиви и функции. Следователно концепцията за обратно извикване ни позволява да предадем функцията като аргумент на друга функция. Функцията за обратно извикване ще завърши първо изпълнението, а родителската функция ще бъде изпълнена по-късно.
Функциите за обратно извикване се изпълняват асинхронно и позволяват на кода да продължи да работи, без да чака да завърши асинхронната задача. Когато се комбинират множество асинхронни задачи и всяка задача зависи от предишната си задача, структурата на кода става сложна.
Нека разберем употребата и значението на обратните извиквания. Да предположим, че имаме функция, която приема три параметъра, един низ и две числа. Искаме някакъв изход въз основа на текста на низа с множество условия.
Разгледайте примера по-долу:
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
Все пак резултатът ще бъде същият. Но в горния пример ние сме дефинирали тялото на неговата отделна функция и сме предали функцията като функция за обратно извикване към функцията expectResult. Следователно, ако искаме да разширим функционалността на очакваните резултати, така че да можем да създадем друго функциониращо тяло с различна операция и да го използваме като функция за обратно извикване, това ще улесни разбирането и ще подобри четливостта на кода.
Има и други различни примери за обратни извиквания, налични в поддържаните функции на JavaScript. Няколко често срещани примера са слушатели на събития и функции за масиви, като карта, намаляване, филтриране и т.н.
За да го разберем по-добре, трябва да разберем преминаването по стойност и предаването по препратка на 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('btn'); const greet=()=>{ console.log('Hello, How are you?') } button.addEventListener('click', greet)
Изход:
В горния пример сме предали функция greet като аргумент в addEventListener като функция за обратно извикване. Той ще бъде извикан, когато се задейства събитието за кликване.
По подобен начин филтърът също е пример за функцията за обратно извикване. Ако използваме филтър за итериране на масив, той ще приеме друга функция за обратно извикване като аргумент за обработка на данните от масива. Разгледайте примера по-долу; в този пример използваме функцията по-голямо, за да отпечатаме числото, по-голямо от 5 в масива. Използваме функцията isGreater като функция за обратно извикване във филтърния метод.
const arr = [3,10,6,7] const isGreater = num => num > 5 console.log(arr.filter(isGreater))
Изход:
[ 10, 6, 7 ]
Горният пример показва, че функцията по-голяма се използва като функция за обратно извикване в метода на филтъра.
За да разберем по-добре обратните извиквания, циклите на събития в JavaScript, нека обсъдим синхронния и асинхронния JavaScript:
Синхронен JavaScript
Нека да разберем какви са характеристиките на езика за синхронно програмиране. Синхронното програмиране има следните характеристики:
Блокиране на изпълнението: Езикът за синхронно програмиране поддържа техниката на блокиране на изпълнението, което означава, че блокира изпълнението на последващи изрази, като съществуващите оператори ще бъдат изпълнени. По този начин се постига предвидимо и детерминистично изпълнение на изразите.
Последователен поток: Синхронното програмиране поддържа последователния поток на изпълнение, което означава, че всеки оператор се изпълнява последователно като един след друг. Езиковата програма изчаква даден оператор да завърши, преди да премине към следващия.
Простота: Често синхронното програмиране се смята за лесно за разбиране, защото можем да предскажем неговия ред на потока на изпълнение. Като цяло е линеен и лесен за прогнозиране. Малките приложения са добри за разработване на тези езици, защото могат да се справят с критичния ред на операциите.
низ в java методи
Директно обработване на грешки: В синхронния език за програмиране обработката на грешки е много лесна. Ако възникне грешка, когато се изпълнява оператор, тя ще изведе грешка и програмата може да я улови.
Накратко, синхронното програмиране има две основни функции, т.е. една задача се изпълнява наведнъж и следващият набор от следващи задачи ще бъде адресиран едва след като текущата задача бъде завършена. По този начин следва последователно изпълнение на код.
Това поведение на програмирането, когато се изпълнява израз, езикът създава ситуация на блоков код, тъй като всяка задача трябва да изчака предишната работа да бъде завършена.
Но когато хората говорят за JavaScript, винаги е бил озадачаващ отговорът дали е синхронен или асинхронен.
В обсъдените по-горе примери, когато използвахме функция като обратно извикване във филтърната функция, тя беше изпълнена синхронно. Следователно се нарича синхронно изпълнение. Филтърната функция трябва да изчака по-голямата функция да завърши своето изпълнение.
Следователно функцията за обратно извикване също се нарича блокиране на обратни извиквания, тъй като блокира изпълнението на родителската функция, в която е била извикана.
На първо място, JavaScript се счита за еднопоточен синхронен и блокиращ по природа. Но използвайки няколко подхода, можем да го накараме да работи асинхронно въз основа на различни сценарии.
Сега нека разберем асинхронния JavaScript.
Асинхронен JavaScript
Асинхронният език за програмиране се фокусира върху подобряване на производителността на приложението. Обратните извиквания могат да се използват в такива сценарии. Можем да анализираме асинхронното поведение на JavaScript чрез примера по-долу:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000)
От горния пример функцията setTimeout приема обратно извикване и време в милисекунди като аргументи. Обратното извикване се извиква след споменатото време (тук 1s). Накратко, функцията ще изчака 1s за своето изпълнение. Сега погледнете кода по-долу:
function greet(){ console.log('greet after 1 second') } setTimeout(greet, 1000) console.log('first') console.log('Second')
Изход:
first Second greet after 1 second
От горния код, съобщенията в журнала след setTimeout ще бъдат изпълнени първи, докато таймерът ще премине. Следователно се получава една секунда и след това поздравителното съобщение след интервал от 1 секунда.
В JavaScript setTimeout е асинхронна функция. Всеки път, когато извикаме функцията setTimeout, тя регистрира функция за обратно извикване (greet в този случай), която да бъде изпълнена след определеното забавяне. Въпреки това, той не блокира изпълнението на последващия код.
В горния пример съобщенията в журнала са синхронните оператори, които се изпълняват незабавно. Те не зависят от функцията setTimeout. Следователно те изпълняват и регистрират съответните си съобщения в конзолата, без да чакат забавянето, посочено в setTimeout.
Междувременно цикълът на събитията в JavaScript обработва асинхронните задачи. В този случай той изчаква определеното забавяне (1 секунда) да премине и след изтичането на това време избира функцията за обратно извикване (greet) и я изпълнява.
чист npm кеш
По този начин другият код след функцията setTimeout се изпълняваше, докато работи във фонов режим. Това поведение позволява на JavaScript да изпълнява други задачи, докато чака асинхронната операция да завърши.
Трябва да разберем стека на повикванията и опашката за обратно извикване, за да обработваме асинхронните събития в JavaScript.
Разгледайте изображението по-долу:
От изображението по-горе типичният двигател на JavaScript се състои от куп памет и стек за повиквания. Стекът за извикване изпълнява целия код, без да чака, когато бъде избутан към стека.
Heap паметта е отговорна за разпределянето на паметта за обекти и функции по време на изпълнение, когато са необходими.
Сега нашите двигатели на браузъра се състоят от няколко уеб API, като DOM, setTimeout, конзола, извличане и т.н., и двигателят има достъп до тези API чрез глобалния обект на прозореца. В следващата стъпка някои цикли на събития играят ролята на пазач, който избира заявки за функции в опашката за обратно извикване и ги избутва в стека. Тези функции, като setTimeout, изискват определено време за изчакване.
Сега нека се върнем към нашия пример, функцията setTimeout; когато функцията бъде открита, таймерът се регистрира в опашката за обратно извикване. След това останалата част от кода се избутва в стека за извикване и се изпълнява, след като функцията достигне лимита на таймера си, изтече и опашката за обратно извикване избутва функцията за обратно извикване, която има указаната логика и е регистрирана във функцията за изчакване . По този начин той ще бъде изпълнен след определеното време.
Адски сценарии за обратно извикване
Сега обсъдихме обратните извиквания, синхронните, асинхронните и други подходящи теми за ада на обратното извикване. Нека разберем какво е адът на обратното извикване в JavaScript.
Ситуацията, когато са вложени множество обратни извиквания, е известна като ада на обратното извикване, тъй като формата на кода изглежда като пирамида, която също се нарича „пирамидата на гибелта“.
Адът на обратното извикване прави по-трудно разбирането и поддържането на кода. Най-вече можем да видим тази ситуация, докато работим в възел JS. Например, разгледайте примера по-долу:
getArticlesData(20, (articles) => { console.log('article lists', articles); getUserData(article.username, (name) => { console.log(name); getAddress(name, (item) => { console.log(item); //This goes on and on... } })
В горния пример getUserData взема потребителско име, което зависи от списъка със статии или трябва да бъде извлечен отговор на getArticles, който е вътре в статията. getAddress също има подобна зависимост, която зависи от отговора на getUserData. Тази ситуация се нарича ад за обратно извикване.
Вътрешната работа на ада за обратно извикване може да бъде разбрана с примера по-долу:
Нека разберем, че трябва да изпълним задача A. За да изпълним задача, имаме нужда от някои данни от задача B. По същия начин; имаме различни задачи, които са зависими една от друга и се изпълняват асинхронно. По този начин той създава серия от функции за обратно извикване.
Нека разберем Promises в JavaScript и как създават асинхронни операции, което ни позволява да избягваме писането на вложени обратни извиквания.
JavaScript обещания
В JavaScript обещанията бяха въведени в ES6. Това е обект със синтактично покритие. Поради асинхронното си поведение това е алтернативен начин за избягване на писането на обратни извиквания за асинхронни операции. В днешно време уеб приложни програмни интерфейси като fetch() се внедряват с помощта на обещаващия, който осигурява ефективен начин за достъп до данните от сървъра. Той също така подобри четливостта на кода и е начин да се избегне писането на вложени обратни извиквания.
python запазва json във файл
Обещанията в реалния живот изразяват доверие между двама или повече лица и увереност, че определено нещо със сигурност ще се случи. В JavaScript Promise е обект, който гарантира производството на една стойност в бъдеще (когато е необходимо). Promise в JavaScript се използва за управление и справяне с асинхронни операции.
Обещанието връща обект, който гарантира и представя завършването или неуспеха на асинхронни операции и неговия изход. Това е прокси за стойност, без да знае точния изход. Полезно е за асинхронни действия да предоставят стойност за евентуален успех или причина за неуспех. По този начин асинхронните методи връщат стойностите като синхронен метод.
Обикновено обещанията имат следните три състояния:
- Изпълнено: Изпълненото състояние е, когато приложено действие е разрешено или завършено успешно.
- Чакащо: състоянието на чакане е, когато заявката е в процес и приложеното действие не е нито разрешено, нито отхвърлено и все още е в първоначалното си състояние.
- Отхвърлено: Отхвърленото състояние е, когато приложеното действие е отхвърлено, което води до неуспех на желаната операция. Причината за отхвърляне може да бъде всичко, включително сървърът да не работи.
Синтаксисът за обещанията:
let newPromise = new Promise(function(resolve, reject) { // asynchronous call is made //Resolve or reject the data });
По-долу е даден пример за писане на обещанията:
Това е пример за писане на обещание.
function getArticleData(id) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('Fetching data....'); resolve({ id: id, name: 'derik' }); }, 5000); }); } getArticleData('10').then(res=> 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('Error: ', err.message); } } displayData();
За да използвате Async/Await, функцията трябва да бъде указана с ключовата дума async, а ключовата дума await трябва да бъде написана във функцията. Async ще спре своето изпълнение, докато Promise не бъде разрешено или отхвърлено. Ще бъде възобновено, когато Обещанието бъде раздадено. Веднъж разрешена, стойността на израза await ще бъде съхранена в променливата, която я съдържа.
Резюме:
С две думи, можем да избегнем вложени обратни извиквания, като използваме обещанията и async/await. Освен тях, можем да следваме и други подходи, като писане на коментари, а разделянето на кода на отделни компоненти също може да бъде полезно. Но в днешно време разработчиците предпочитат използването на async/await.
Заключение:
Адът на обратното извикване в JavaScript се нарича ситуация, при която се изпълнява прекомерно количество вложени функции за обратно извикване. Това намалява четливостта на кода и поддръжката. Ситуацията на ада с обратно извикване обикновено възниква, когато се работи с асинхронни операции на заявки, като например правене на множество API заявки или обработка на събития със сложни зависимости.
За да разберете по-добре ада за обратно извикване в JavaScript.
JavaScript разглежда всичко като обект, като низове, масиви и функции. Следователно концепцията за обратно извикване ни позволява да предадем функцията като аргумент на друга функция. Функцията за обратно извикване ще завърши първо изпълнението, а родителската функция ще бъде изпълнена по-късно.
Функциите за обратно извикване се изпълняват асинхронно и позволяват на кода да продължи да работи, без да чака да завърши асинхронната задача.