# Създаване на банково приложение, част 3: Методи за извличане и използване на данни Помислете за компютъра на кораба Enterprise в Star Trek - когато капитан Пикард поиска информация за състоянието на кораба, тя се появява мигновено, без интерфейсът да се затваря и да се изгражда отново. Точно този плавен поток от информация се стремим да създадем тук с динамично извличане на данни. В момента вашето банково приложение е като печатен вестник - информативно, но статично. Ще го трансформираме в нещо подобно на контролния център на НАСА, където данните текат непрекъснато и се актуализират в реално време, без да прекъсват работния процес на потребителя. Ще научите как да комуникирате със сървъри асинхронно, да обработвате данни, които пристигат в различни моменти, и да трансформирате сурова информация в нещо значимо за вашите потребители. Това е разликата между демонстрация и софтуер, готов за производство. ## Предварителен тест [Предварителен тест](https://ff-quizzes.netlify.app/web/quiz/45) ### Предпоставки Преди да се потопите в извличането на данни, уверете се, че разполагате със следните компоненти: - **Предишен урок**: Завършете [Форма за вход и регистрация](../2-forms/README.md) - ще надграждаме върху тази основа - **Локален сървър**: Инсталирайте [Node.js](https://nodejs.org) и [стартирайте API сървъра](../api/README.md), за да предоставите данни за акаунти - **API връзка**: Тествайте връзката със сървъра с тази команда: ```bash curl http://localhost:5000/api # Expected response: "Bank API v1.0.0" ``` Този бърз тест гарантира, че всички компоненти комуникират правилно: - Проверява дали Node.js работи правилно на вашата система - Потвърждава, че вашият API сървър е активен и отговаря - Валидира, че вашето приложение може да достигне до сървъра (като проверка на радиовръзка преди мисия) --- ## Разбиране на извличането на данни в съвременните уеб приложения Начинът, по който уеб приложенията обработват данни, се е развил значително през последните две десетилетия. Разбирането на тази еволюция ще ви помогне да оцените защо съвременните техники като AJAX и Fetch API са толкова мощни и защо са станали основни инструменти за уеб разработчиците. Нека разгледаме как работеха традиционните уебсайтове в сравнение с динамичните, отзивчиви приложения, които изграждаме днес. ### Традиционни многостранични приложения (MPA) В ранните дни на уеб пространството всяко кликване беше като смяна на канали на стар телевизор - екранът се изключваше, след което бавно се настройваше към новото съдържание. Това беше реалността на ранните уеб приложения, където всяко взаимодействие означаваше пълно преизграждане на цялата страница от нулата. ```mermaid sequenceDiagram participant User participant Browser participant Server User->>Browser: Clicks link or submits form Browser->>Server: Requests new HTML page Note over Browser: Page goes blank Server->>Browser: Returns complete HTML page Browser->>User: Displays new page (flash/reload) ``` ![Работен процес на актуализация в многостранично приложение](../../../../translated_images/mpa.7f7375a1a2d4aa779d3f928a2aaaf9ad76bcdeb05cfce2dc27ab126024050f51.bg.png) **Защо този подход изглеждаше тромав:** - Всяко кликване означаваше пълно преизграждане на цялата страница - Потребителите бяха прекъсвани в мислите си от досадни мигания на страницата - Вашата интернет връзка работеше извънредно, като изтегляше едни и същи заглавия и долни колонтитули многократно - Приложенията се усещаха повече като прелистване на картотека, отколкото като използване на софтуер ### Съвременни едностранични приложения (SPA) AJAX (Asynchronous JavaScript and XML) промени този парадигм изцяло. Подобно на модулния дизайн на Международната космическа станция, където астронавтите могат да заменят отделни компоненти, без да изграждат цялата структура отново, AJAX ни позволява да актуализираме конкретни части от уеб страница, без да презареждаме всичко. Въпреки че името споменава XML, днес най-често използваме JSON, но основният принцип остава: актуализирайте само това, което трябва да се промени. ```mermaid sequenceDiagram participant User participant Browser participant JavaScript participant Server User->>Browser: Interacts with page Browser->>JavaScript: Triggers event handler JavaScript->>Server: Fetches only needed data Server->>JavaScript: Returns JSON data JavaScript->>Browser: Updates specific page elements Browser->>User: Shows updated content (no reload) ``` ![Работен процес на актуализация в едностранично приложение](../../../../translated_images/spa.268ec73b41f992c2a21ef9294235c6ae597b3c37e2c03f0494c2d8857325cc57.bg.png) **Защо SPA се усещат толкова по-добре:** - Актуализират се само частите, които наистина са се променили (умно, нали?) - Няма повече резки прекъсвания - потребителите остават в своя поток - По-малко данни се предават по мрежата, което означава по-бързо зареждане - Всичко се усеща бързо и отзивчиво, като приложенията на вашия телефон ### Еволюцията към съвременния Fetch API Съвременните браузъри предоставят [`Fetch` API](https://developer.mozilla.org/docs/Web/API/Fetch_API), който заменя стария [`XMLHttpRequest`](https://developer.mozilla.org/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest). Подобно на разликата между работа с телеграф и използване на имейл, Fetch API използва promises за по-чист асинхронен код и обработва JSON естествено. | Функция | XMLHttpRequest | Fetch API | |---------|----------------|----------| | **Синтаксис** | Сложен, базиран на callback | Чист, базиран на promises | | **Обработка на JSON** | Изисква ръчно парсиране | Вграденият метод `.json()` | | **Обработка на грешки** | Ограничена информация за грешки | Подробна информация за грешки | | **Съвременна поддръжка** | Съвместимост с наследени системи | ES6+ promises и async/await | > 💡 **Съвместимост с браузъри**: Добри новини - Fetch API работи във всички съвременни браузъри! Ако сте любопитни за конкретни версии, [caniuse.com](https://caniuse.com/fetch) има пълната история на съвместимостта. > **Основното:** - Работи отлично в Chrome, Firefox, Safari и Edge (на практика навсякъде, където са вашите потребители) - Само Internet Explorer се нуждае от допълнителна помощ (и честно казано, време е да се сбогуваме с IE) - Перфектно се вписва в елегантните async/await модели, които ще използваме по-късно ### Имплементиране на система за вход и извличане на данни Сега нека имплементираме система за вход, която ще трансформира вашето банково приложение от статичен дисплей в функционално приложение. Подобно на протоколите за автентикация, използвани в сигурни военни съоръжения, ще проверим идентификационните данни на потребителя и след това ще предоставим достъп до специфичните му данни. Ще изградим това постепенно, започвайки с основна автентикация и след това добавяйки възможности за извличане на данни. #### Стъпка 1: Създаване на основа за функцията за вход Отворете файла `app.js` и добавете нова функция `login`. Тя ще обработва процеса на автентикация на потребителя: ```javascript async function login() { const loginForm = document.getElementById('loginForm'); const user = loginForm.user.value; } ``` **Нека разгледаме това подробно:** - Ключовата дума `async`? Тя казва на JavaScript "хей, тази функция може да се наложи да изчака някои неща" - Намираме нашата форма на страницата (нищо сложно, просто я намираме по нейния ID) - След това извличаме каквото потребителят е въвел като потребителско име - Ето един интересен трик: можете да достъпите всяко поле на формата чрез атрибута `name` - няма нужда от допълнителни извиквания на getElementById! > 💡 **Модел за достъп до формата**: Всеки контрол на формата може да бъде достъпен чрез неговото име (зададено в HTML чрез атрибута `name`) като свойство на елемента на формата. Това предоставя чист и четим начин за извличане на данни от формата. #### Стъпка 2: Създаване на функция за извличане на данни за акаунта След това ще създадем специална функция за извличане на данни за акаунта от сървъра. Това следва същия модел като вашата функция за регистрация, но се фокусира върху извличането на данни: ```javascript async function getAccount(user) { try { const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user)); return await response.json(); } catch (error) { return { error: error.message || 'Unknown error' }; } } ``` **Ето какво постига този код:** - **Използва** съвременния `fetch` API за асинхронно заявяване на данни - **Конструира** URL за GET заявка с параметър за потребителско име - **Прилага** `encodeURIComponent()`, за да обработва безопасно специални символи в URL адреси - **Конвертира** отговора в JSON формат за лесна манипулация на данни - **Обработва** грешки грациозно, като връща обект за грешка вместо да се срине > ⚠️ **Бележка за сигурност**: Функцията `encodeURIComponent()` обработва специални символи в URL адреси. Подобно на системите за кодиране, използвани в морските комуникации, тя гарантира, че вашето съобщение пристига точно както е предвидено, предотвратявайки неправилно интерпретиране на символи като "#" или "&". > **Защо това е важно:** - Предотвратява специални символи да нарушат URL адресите - Защитава срещу атаки чрез манипулация на URL адреси - Гарантира, че вашият сървър получава предвидените данни - Следва практики за сигурно програмиране #### Разбиране на HTTP GET заявки Ето нещо, което може да ви изненада: когато използвате `fetch` без допълнителни опции, той автоматично създава [`GET`](https://developer.mozilla.org/docs/Web/HTTP/Methods/GET) заявка. Това е идеално за това, което правим - питаме сървъра "хей, мога ли да видя данните за акаунта на този потребител?" Помислете за GET заявките като учтиво искане за заемане на книга от библиотеката - искате да видите нещо, което вече съществува. POST заявките (които използвахме за регистрация) са повече като подаване на нова книга, която да бъде добавена към колекцията. | GET заявка | POST заявка | |-------------|-------------| | **Цел** | Извличане на съществуващи данни | Изпращане на нови данни към сървъра | | **Параметри** | В URL път/заявка | В тялото на заявката | | **Кеширане** | Може да бъде кеширано от браузъри | Обикновено не се кешира | | **Сигурност** | Видимо в URL/логове | Скрито в тялото на заявката | #### Стъпка 3: Свързване на всичко Сега за удовлетворяващата част - нека свържем вашата функция за извличане на акаунт с процеса на вход. Тук всичко се събира на едно място: ```javascript async function login() { const loginForm = document.getElementById('loginForm'); const user = loginForm.user.value; const data = await getAccount(user); if (data.error) { return console.log('loginError', data.error); } account = data; navigate('/dashboard'); } ``` Тази функция следва ясен ред: - Извлича потребителското име от входа на формата - Заявява данните за акаунта на потребителя от сървъра - Обработва всякакви грешки, които възникват по време на процеса - Съхранява данните за акаунта и навигира към таблото за управление при успех > 🎯 **Модел Async/Await**: Тъй като `getAccount` е асинхронна функция, използваме ключовата дума `await`, за да спрем изпълнението, докато сървърът отговори. Това предотвратява продължаването на кода с неопределени данни. #### Стъпка 4: Създаване на място за вашите данни Вашето приложение се нуждае от място, където да запомни информацията за акаунта, след като бъде заредена. Помислете за това като за краткосрочната памет на вашето приложение - място, където да държите данните на текущия потребител под ръка. Добавете този ред в началото на вашия файл `app.js`: ```javascript // This holds the current user's account data let account = null; ``` **Защо ни е нужно това:** - Държи данните за акаунта достъпни от всяка точка на вашето приложение - Започването с `null` означава "никой не е влязъл още" - Актуализира се, когато някой успешно влезе или се регистрира - Действа като единствен източник на истина - няма объркване кой е влязъл #### Стъпка 5: Свързване на вашата форма Сега нека свържем вашата нова функция за вход към вашата HTML форма. Актуализирайте тагът на формата по следния начин: ```html
``` **Какво прави тази малка промяна:** - Спира формата да изпълнява стандартното си поведение "презареждане на цялата страница" - Извиква вашата персонализирана JavaScript функция вместо това - Поддържа всичко гладко и в стил на едностранично приложение - Дава ви пълен контрол над това, което се случва, когато потребителите натиснат "Вход" #### Стъпка 6: Подобряване на функцията за регистрация За последователност, актуализирайте вашата функция `register`, за да съхранява данни за акаунта и да навигира към таблото за управление: ```javascript // Add these lines at the end of your register function account = result; navigate('/dashboard'); ``` **Това подобрение предоставя:** - **Плавен** преход от регистрация към таблото за управление - **Последователно** потребителско изживяване между потоците за вход и регистрация - **Незабавен** достъп до данни за акаунта след успешна регистрация #### Тестване на вашата имплементация ```mermaid flowchart TD A[User enters credentials] --> B[Login function called] B --> C[Fetch account data from server] C --> D{Data received successfully?} D -->|Yes| E[Store account data globally] D -->|No| F[Display error message] E --> G[Navigate to dashboard] F --> H[User stays on login page] ``` **Време е да го изпробвате:** 1. Създайте нов акаунт, за да се уверите, че всичко работи 2. Опитайте да влезете с тези същите идентификационни данни 3. Погледнете конзолата на браузъра си (F12), ако нещо изглежда странно 4. Уверете се, че стигате до таблото за управление след успешен вход Ако нещо не работи, не се паникьосвайте! Повечето проблеми са лесни за поправяне, като например правописни грешки или забравяне да стартирате API сървъра. #### Бърза дума за магията на Cross-Origin Може би се чудите: "Как моето уеб приложение говори с този API сървър, когато те работят на различни портове?" Отличен въпрос! Това засяга нещо, с което всеки уеб разработчик се сблъсква рано или късно. > 🔒 **Сигурност на Cross-Origin**: Браузърите налагат "политика за същия произход", за да предотвратят неоторизирана комуникация между различни домейни. Подобно на системата за проверка в Пентагона, те проверяват дали комуникацията е разрешена, преди да позволят прехвърляне на данни. > **В нашата конфигурация:** - Вашето уеб приложение работи на `localhost:3000` (сървър за разработка) - Вашият API сървър работи на `localhost:5000` (бекенд сървър) - API сървърът включва [CORS заглавия](https://developer.mozilla.org/docs/Web/HTTP/CORS), които изрично разрешават комуникация от вашето уеб приложение Тази конфигурация отразява реалния свят на разработка, където фронтенд и бекенд приложения обикновено работят на отделни сървъри. > 📚 **Научете повече**: За по-сложно съдържание, комбинирайте [`document.createElement()`](https://developer.mozilla.org/docs/Web/API/Document/createElement) с метода [`append()`](https://developer.mozilla.org/docs/Web/API/ParentNode/append): ```javascript // Safe way to create new elements const transactionItem = document.createElement('div'); transactionItem.className = 'transaction-item'; transactionItem.textContent = `${transaction.date}: ${transaction.description}`; container.append(transactionItem); ``` **Разбиране на този подход:** - **Създава** нови DOM елементи програмно - **Поддържа** пълен контрол върху атрибутите и съдържанието на елементите - **Позволява** сложни, вложени структури на елементи - **Осигурява** сигурност чрез разделяне на структурата от съдържанието > ⚠️ **Съображения за сигурност**: Въпреки че [`innerHTML`](https://developer.mozilla.org/docs/Web/API/Element/innerHTML) често се среща в много уроци, той може да изпълнява вградени скриптове. Подобно на протоколите за сигурност в CERN, които предотвратяват неоторизирано изпълнение на код, използването на `textContent` и `createElement` предоставя по-безопасни алтернативи. > **Рискове на innerHTML:** - Изпълнява всякакви `