Един от ключовете за писане на успешно уеб приложение е възможността да се правят десетки AJAX повиквания на страница.
Това е типично предизвикателство за асинхронно програмиране и начинът, по който решите да се справите с асинхронните повиквания, до голяма степен ще направи или счупи приложението ви и, като разширение, потенциално цялото ви стартиране.
Синхронизирането на асинхронни задачи в JavaScript беше сериозен проблем за много дълго време.
Това предизвикателство засяга back-end разработчици, използващи Node.js колкото разработчиците от предния край, използващи която и да е JavaScript рамка. Асинхронното програмиране е част от нашата ежедневна работа, но предизвикателството често се приема лекомислено и не се разглежда в точния момент.
Първото и най-прякото решение дойде под формата на вложени функции като обратно извикване . Това решение доведе до нещо, наречено обратно обаждане ад и твърде много приложения все още усещат изгарянето му.
След това имаме Обещания . Този модел направи кода много по-лесен за четене, но беше далеч от принципа Don’t Repeat Yourself (DRY). Все още имаше твърде много случаи, когато трябваше да повторите едни и същи парчета код, за да управлявате правилно потока на приложението. Най-новото допълнение, под формата на асинхронни / изчакващи изявления, накрая направи асинхронния код в JavaScript толкова лесен за четене и запис, колкото и всяка друга част от кода.
Нека да разгледаме примерите на всяко от тези решения и да размислим върху развитието на асинхронното програмиране в JavaScript.
За целта ще разгледаме проста задача, която изпълнява следните стъпки:
Древното решение за синхронизиране на тези повиквания беше чрез вложени обратни извиквания. Това беше приличен подход за прости асинхронни задачи на JavaScript, но нямаше да се мащабира поради проблем, наречен обратно обаждане ад .
Кодът за трите прости задачи ще изглежда по следния начин:
const verifyUser = function(username, password, callback){ dataBase.verifyUser(username, password, (error, userInfo) => { if (error) { callback(error) }else{ dataBase.getRoles(username, (error, roles) => { if (error){ callback(error) }else { dataBase.logAccess(username, (error) => { if (error){ callback(error); }else{ callback(null, userInfo, roles); } }) } }) } }) };
Всяка функция получава аргумент, който е друга функция, която се извиква с параметър, който е отговорът на предишното действие.
Твърде много хора ще изпитат замръзване на мозъка само като прочетат изречението по-горе. Наличието на приложение със стотици подобни кодови блокове ще доведе до още повече проблеми на човека, поддържащ кода, дори ако той сам го е написал.
Този пример става още по-сложен, след като разберете, че database.getRoles
е друга функция, която има вложени обратни извиквания.
const getRoles = function (username, callback){ database.connect((connection) => { connection.query('get roles sql', (result) => { callback(null, result); }) }); };
В допълнение към кода, който е труден за поддръжка, принципът DRY няма абсолютно никаква стойност в този случай. Обработката на грешки, например, се повтаря във всяка функция и основният обратен извикване се извиква от всяка вложена функция.
По-сложните асинхронни JavaScript операции, като цикъл през асинхронни повиквания, е още по-голямо предизвикателство. Всъщност няма тривиален начин да се направи това с обратно извикване. Ето защо библиотеките на JavaScript Promise харесват Синя птица и Въпрос: има толкова много сцепление. Те осигуряват начин за извършване на общи операции над асинхронни заявки, които самият език вече не предоставя.
Това е мястото, където влизат обещания за естествен JavaScript.
Обещания бяха следващата логична стъпка в бягството от ада за обратно извикване. Този метод не премахна използването на обратно извикване, но направи веригата от функции ясна и опрости кода , което улеснява четенето.
С обещанията на място кодът в нашия пример за асинхронен JavaScript ще изглежда по следния начин:
const verifyUser = function(username, password) { database.verifyUser(username, password) .then(userInfo => dataBase.getRoles(userInfo)) .then(rolesInfo => dataBase.logAccess(rolesInfo)) .then(finalResult => { //do whatever the 'callback' would do }) .catch((err) => { //do whatever the error handler needs }); };
За да се постигне този вид простота, всички функции, използвани в примера, трябва да бъдат Обещан . Нека да разгледаме как getRoles
методът ще бъде актуализиран, за да върне Promise
:
const getRoles = function (username){ return new Promise((resolve, reject) => { database.connect((connection) => { connection.query('get roles sql', (result) => { resolve(result); }) }); }); };
Променихме метода, за да върнем Promise
, с две обратни извиквания и Promise
сам изпълнява действия от метода. Сега, resolve
и reject
обратните обаждания ще бъдат преобразувани в Promise.then
и Promise.catch
методи съответно.
Може да забележите, че getRoles
метод все още е вътрешно склонен към феномена на пирамидата на обречеността. Това се дължи на начина, по който се създават методите на базата данни, тъй като те не връщат Promise
. Ако нашите методи за достъп до база данни също се върнат Promise
getRoles
метод ще изглежда по следния начин:
const getRoles = new function (userInfo) { return new Promise((resolve, reject) => { database.connect() .then((connection) => connection.query('get roles sql')) .then((result) => resolve(result)) .catch(reject) }); };
Пирамидата на обречеността беше значително смекчена с въвеждането на Promises. Все пак трябваше да разчитаме на обратно извикване, което се предава на .then
и .catch
методи на Promise
.
Обещанията проправиха път към едно от най-страхотните подобрения в JavaScript. ECMAScript 2017 донесе синтактична захар на върха на Promises в JavaScript под формата на async
и await
изявления.
Те ни позволяват да напишем Promise
базиран код, сякаш е синхронен, но без да блокира основната нишка, тъй като този пример на кода демонстрира:
const verifyUser = async function(username, password){ try { const userInfo = await dataBase.verifyUser(username, password); const rolesInfo = await dataBase.getRoles(userInfo); const logStatus = await dataBase.logAccess(userInfo); return userInfo; }catch (e){ //handle errors as needed } };
В очакване Promise
разрешаването е разрешено само в рамките на async
функции, което означава, че verifyUser
трябваше да бъде дефиниран с помощта на async function
.
Щом обаче е направена тази малка промяна, можете да await
всеки Promise
без допълнителни промени в други методи.
Асинхронните функции са следващата логическа стъпка в еволюцията на асинхронното програмиране в JavaScript. Те ще направят кода ви много по-чист и по-лесен за поддръжка. Деклариране на функция като async
ще гарантира, че винаги връща Promise
за да не се притеснявате повече за това.
Защо трябва да започнете да използвате JavaScript async
функция днес?
try
/ catch
точно както във всеки друг синхронен код..then
блокът няма да премине към следващия .then
защото стъпва само през синхронен код. Но можете да преминете през await
разговори, сякаш са синхронни разговори.Операторите Async / await са синтактична захар, създадена върху JavaScript Promises. Те ни позволяват да пишем базиран на Promise код, сякаш е синхронен, но без да блокира основната нишка.
В JavaScript адът за обратно извикване е анти-шаблон в кода, който се случва в резултат на лошо структуриране на асинхронен код. Обикновено се наблюдава, когато програмистите се опитват да принудят визуална структура отгоре надолу в техния асинхронен JavaScript код, базиран на обратно извикване.
Обещанието в JavaScript е като стойност на заместител, която се очаква в крайна сметка да се превърне в крайната успешна стойност на резултата или причина за неуспех.