Ось готовий урок, створений за твоїм майстер-промптом.
🎓 Урок CS50-style: Асинхронність у FastAPI (async/await)
Привіт, друзі! Радий бачити вас знову.
Сьогодні ми зазирнемо під капот сучасної веб-розробки. Ми поговоримо про тему, яка робить FastAPI справді "Fast". Це тема, яка відрізняє повільний, неповороткий сервер від того, що здатен обробляти тисячі запитів за секунду на одному-єдиному процесорі.
Ми говоримо про Асинхронність.
1. 🔥 Вступ: Чому черга за кавою — це погано?
Уявіть, що ви прийшли в найпопулярнішу кав'ярню міста зранку. Там працює лише один бариста — назвемо його Python.
Сценарій А (Синхронний світ): Ви замовляєте лате. Бариста приймає замовлення, повертається до кавомашини, меле зерна, збиває молоко, наливає каву... Це займає 3 хвилини. Увесь цей час він нічого більше не робить. Він просто дивиться, як тече еспресо. Черга з 50 людей стоїть і чекає. Ніхто не може навіть зробити замовлення, поки ви не отримаєте свій стаканчик.
Як вам такий сервіс? Жахливо, правда?
Сценарій Б (Асинхронний світ): Ви замовляєте лате. Бариста натискає кнопку на кавомашині (запускає процес) і... одразу повертається до каси, щоб прийняти замовлення наступного клієнта. Поки ваша кава готується (процес іде сам по собі), він встигає прийняти ще 5 замовлень. Як тільки ваша кава готова — він віддає її вам.
Питання до вас:
Якщо ви пишете сервер для обробки тисяч користувачів, якого "баристу" ви хочете найняти?
Звісно, другого. У світі Python цей "розумний бариста" — це і є async/await у FastAPI. Без цього ваш код буде "блокувати" всіх інших користувачів, поки один чекає відповідь від бази даних.
2. 🧠 Теоретична база: Як це працює?
Давайте без зайвих академічних термінів. Що відбувається "під капотом"?
У Python (і у FastAPI) є головний менеджер, який називається Event Loop (Цикл подій). Уявіть його як менеджера в тій самій кав'ярні. Він бігає зі списком задач.
Ключові поняття:
async def(Асинхронна функція / Корутина): Коли ви пишетеasyncперед функцією, ви кажете Python'у: "Гей, ця функція може виконуватися довго, але вона вміє ставити себе на паузу".await(Очікування): Це ключовий момент. Коли ви пишетеawait get_data_from_db(), ви кажете: "Я тут почекаю результат. Але поки я чекаю, Event Loop, іди виконуй інші задачі!".
Що треба зрозуміти інтуїтивно:
Асинхронність — це не виконання декількох речей одночасно (паралельно) на різних ядрах процесора. Це вміння ефективно чекати.
Веб-сервери 99% часу чекають: * Чекають відповідь від бази даних. * Чекають відповідь від іншого API. * Чекають запису файлу на диск.
Асинхронність дозволяє процесору не "спати" під час цього очікування, а перемикатися на інші корисні справи.
3. 🧪 Приклади: Від простого до магії
Давайте подивимось на код.
Приклад 1: "Поганий" (Синхронний) код
Уявіть ендпоінт, який імітує довгу роботу (наприклад, складний запит до БД).
import time
from fastapi import FastAPI
app = FastAPI()
@app.get("/sync")
def slow_sync():
# Імітуємо роботу 3 секунди
time.sleep(3)
return {"message": "Я прокинувся!"}
Що тут відбудеться? Якщо 10 користувачів одночасно постукають сюди, перший отримає відповідь через 3 секунди. Другий — через 6 (3+3). Десятий — через 30 секунд! Ваш сервер "завис" для всіх, крім одного.
Приклад 2: "Хороший" (Асинхронний) код
Виправляємо це за допомогою магії async/await.
import asyncio
from fastapi import FastAPI
app = FastAPI()
@app.get("/async")
async def slow_async():
# await каже: "Можеш зайнятися чимось іншим, поки я сплю"
await asyncio.sleep(3)
return {"message": "Я прокинувся асинхронно!"}
А що тут?
Якщо 10 користувачів прийдуть одночасно:
1. Сервер приймає перший запит, доходить до await, відкладає його і...
2. ...миттєво бере другий запит!
3. У результаті всі 10 користувачів отримають відповідь приблизно через 3 секунди сумарно.
Відчуваєте різницю? 30 секунд проти 3 секунд. Ось навіщо нам це потрібно.
Приклад 3: Реальний кейс (Запит до зовнішнього API)
У реальному житті ми не використовуємо sleep. Ми ходимо в інтернет.
Важливо: Для асинхронних запитів не можна використовувати звичайну бібліотеку
requests, бо вона синхронна (блокуюча). Ми використовуємоhttpx.
import httpx
from fastapi import FastAPI
app = FastAPI()
@app.get("/cat")
async def get_random_cat():
# Створюємо асинхронного клієнта (як браузер)
async with httpx.AsyncClient() as client:
# Чекаємо відповідь, але не блокуємо сервер!
response = await client.get("https://catfact.ninja/fact")
return response.json()
Поки ваш сервер чекає факт про котів від catfact.ninja, він спокійно може обробляти реєстрацію нового користувача на іншому ендпоінті.
4. 🛠 Практична частина
Час забруднити руки кодом! Відкривайте ваш редактор.
Завдання 1: Запуск і відчуття
Створіть файл main.py з кодом із Прикладу 2. Запустіть сервер (uvicorn main:app --reload). Відкрийте декілька вкладок браузера і спробуйте швидко оновити сторінку /async.
Чи відчуваєте ви затримку, що накопичується? Ні? Чудово!
Завдання 2: Знайди помилку Студент написав такий код:
@app.get("/bug")
async def buggy_code():
time.sleep(5) # Увага сюди!
return {"msg": "Oops"}
- Виправте цей код так, щоб він став справді асинхронним.
- Підказка: Чому
time.sleepтут небезпечний, навіть якщо функціяasync? (Бо він блокує Event Loop повністю).
Завдання 3: "Ланцюжок очікувань"
Створіть ендпоінт, який:
1. Чекає 1 секунду.
2. Друкує в консоль "Крок 1 готовий".
3. Чекає ще 2 секунди.
4. Повертає JSON: {"status": "done", "total_time": "3s"}.
Використовуйте await двічі.
Завдання 4: Міні-челендж
Уявіть, що вам треба отримати дані з двох сайтів. Напишіть ендпоінт, який робить запит до https://google.com (просто щоб перевірити статус) і до https://github.com послідовно, використовуючи await.
5. 💡 Мислення як у розробника
Ось де ми переходимо від новачків до профі.
Типова помилка №1: Змішування світів
Найгірше, що ви можете зробити — це використати блокуючу функцію всередині async def.
Якщо ви напишете:
async def bad_practice():
heavy_calculation_fibonacci(100000) # Це рахує процесор 10 секунд!
...ви "повісите" весь сервер. Ніхто не отримає відповідь.
Як думає профі: "Якщо це I/O (мережа, диск) — я використовую async/await. Якщо це важкі обчислення (CPU) — я або не роблю це в FastAPI, або виношу в окремий потік (thread)".
Типова помилка №2: Забутий await
Якщо ви викличете асинхронну функцію, але забудете написати await перед нею, ви отримаєте не результат, а об'єкт корутини. Це як отримати чек на каву, але не піти її забирати.
Порада з практики:
Коли обираєте бібліотеки для роботи з базою даних у FastAPI, завжди шукайте ті, що підтримують async (наприклад, Motor для MongoDB або SQLAlchemy в режимі async). Інакше вся магія FastAPI зникне.
6. 🧩 Підсумок
Ну що, давайте підсумуємо, що ми сьогодні розібрали:
- Синхронність — це черга в магазині, де один касир обслуговує по одному.
- Асинхронність — це ресторан, де офіціант приймає замовлення і йде до іншого столика, поки кухня готує.
- У FastAPI ми використовуємо
async def, щоб визначити функцію, іawait, щоб "відпустити" процесор робити інші справи під час очікування. - Це критично важливо для швидкодії при роботі з мережею та базами даних.
Тепер ви вмієте: Створювати ендпоінти, які не "вішають" сервер під час довгих операцій.
Що далі? Зараз ми повертали просто текст. Але реальні додатки працюють з даними. На наступному уроці ми дізнаємось, як валідувати ці дані так, щоб жоден хакер (або неуважний юзер) не зламав вашу систему. Готуйтеся до знайомства з Pydantic Models!
Побачимось на наступному занятті! 🚀