Как брать данные, где брать данные и что такое асинхронное программирование
iframe: https://www.youtube.com/embed/WzjDqrS22Jg
Если вы читали второй урок курса по вёрстке, то вы помните, что весь интернет работает на ХТТП, связке "клиенты-сервер" и, конечно, запросах с ответами.
ХТТП это протокол передачи данных: мы отправляем к серверу запрос и получаем ответ.
Мы — это клиент, а клиентом может быть хоть wget
или curl
из Терминала, хоть браузер. Хоть микроволновка на Андроиде.
Как выглядит запрос? У него есть адрес и метод: GET
, POST
, PUT
, DELETE
, PATCH
, OPTIONS
— вы делаете запрос на адрес и указываете метод.
Зачем это нужно? У каждого ресурса может быть поддержка нескольких методов: например, на POST /users
создаётся новый пользователь, а на GET
— запрашивается список.
GET
— запросить данные,POST
— создать новую сущность,PUT
— обновить существующую,DELETE
— удалить её,PATCH
— частично обновить,OPTIONS
— доступные опции у ресурса
Примеры?
GET /users
— запросить список пользователей,POST /users
— создать пользователя,PUT /users/evgenyrodionov
— обновить пользователяevgenyrodionov
,PATCH /users/evgenyrodionov
— частично обновить пользователяevgenyrodionov
,OPTIONS /users
— запросить опции, доступные на ресурсе (допустим, параметры фильтрации).
Есть много вопросов, давайте на них отвечу. Начнём снизу.
Для этого есть кверипарамс (query params) — это те параметры, которые указываются у адреса, например, GET /users?state=active&offset=20&courseId=react
Кверипарамс это всего лишь строка в формате key=value
и объединением через &
.
В джсе легче всего работать через пакет qs
— он преобразовывает кверипарамс в объект и обратно.
В PUT
уходит новый объект, а в PATCH
— список изменений.
Для этого есть тело запроса, обычно это Джаваскрипт объект (или JSON).
POST /users
{
"username": "evgenyrodionov",
"email": "[email protected]",
"age": 24
}
Для Джсона используется контент-тайп application/json
, но бывают ещё multipart/form-data
(раньше для форм, сейчас в основном для файлов). Что это такое? Это майм-типы.
Для этого используется заголовок content-type: application/json
.
Да, у каждого запроса есть заголовки — это служебная информация для сервера, которую не стоит пихать в тело запроса.
Вот основные:
content-type
— тип тела,accept
— в чём обрабатывается ответ (тоже майм-тип),cookie
— куки,authorization
— авторизационные данные (например, логин и зашифрованный пароль)
Обычно разработчик работает с этими заголовками.
Да.
Один из самых основных это status
.
Статус это код ответа, по которому можно определить насколько успешно прошёл запрос. Кодов много, но вот основные:
Это успешные коды ответа.
- 200 OK — сервер обработал запрос,
- 201 Created — создан новый ресурс,
- 204 No Content — сервер обработал запрос, но ему нечего прислать в теле.
Отвечают за редиректы, нужен заголовок location
с новым адресом.
- 301 Moved Permanently — ресурс навсегда переехал,
- 302 Found — ресурс временно находится по другому адресу.
Запрос был отправлен с ошибкой на стороне клиента (например, неправильно поле заполнено).
- 400 Bad Request — общая ошибка и не очень понятная,
- 401 Unauthorized — человек не авторизован,
- 403 Forbidden — нет доступа,
- 404 Not Found — ресурс или сущность не найдены.
Проблемы на стороне сервера.
- 500 Internal Server Error — такая же общая и слабо понятная ошибка, как 400,
- 501 Not Implemented — ресурс не реализован,
- 502 Bad Gateway — плохой ответ,
- 503 Service Unavailable — сервис временно недоступен, полезно отправить заголовок
retry-after
с времнем в секундах, - 504 Gateway Timeout — ответ не был получен вовремя.
Суммируем.
Веб работает на протоколе ХТТП по системе "клиент-сервер": клиент посылает запрос на адрес, сервер присылает ответ.
Клиент может передать заголовки и тело, а сервер может прислать заголовки и тело.
Клиент это любой инструмент, который работает с ХТТП — хоть другой сервер, хоть утилита в Терминале, хоть бот в Телеграме.
Но как со всем этим работать во фронтэнде?
Вы часто, наверное, слышите про Рест АПИ — мол, наш АПИ он РЕСТФул, мол, полностью по стандартам.
Ирония в том, что любой АПИ, построенный на ХТТП, будет рестовым — в этом и смысл ХТТП. Посмотрите на это определение из Википедии:
REpresentational State Transfer (REST), or RESTful, web services provide interoperability between computer systems on the Internet
В мире нет единого стандарта построения АПИ бэкэнда — каждый дрочит, как он хочет. Нет, серьёзно, посмотрите на эту тьму гайдлайнов: HackerNoon, studioarmix, Некий Филип, Майкрософт, Гугл, RestCase. Тысячи их!
Мне больше всего нравятся архитектура и документация у Страйпа — это эквайринг для приёма платежей. Почитайте как-нибудь.
iframe: https://www.youtube.com/embed/ATYQtAjOgHE
Раньше был XMLHttpRequest — браузерный АПИ для запросов. Неудобный аж жуть.
Поэтому когда появился Джквери с его функцией $.ajax()
, люди выдохнули: наконец-то удобный способ для работы с ХТТП.
Честно говоря, это было одним из поворотных событий, которое привело нас к так называемому Вебу 2.0: интерактивному и без перезагрузок страниц. Веб стал больше похож на приложения, чем на сайты.
Затем этот подход забрали себе библиотеки axios и superagent.
Ну а позже появился новый браузерный АПИ fetch — сейчас его многие и используют. Если нужна поддержка старых браузеров — берут полифилл github/fetch от Гитхаба.
Собственно, Фетч. Фетч это АПИ браузера, которое работает с ХТТП: вы делаете запрос на адрес и обрабатываете ответ.
Чисто технически, это просто одна функция fetch()
, которая построена на промисах. То есть на чём? Об этом позже.
В Фетч вы передаёте адрес и опции: от заголовков до тела запроса, вот пример —
const data = {
username: "evgenyrodionov",
email: "[email protected]",
age: 24
};
fetch("/api/v1/users", {
method: "POST", // метод
body: JSON.stringify(data), // тело
// заголовки
headers: {
"Content-Type": "application/json"
},
// работа с куки
credentials: "same-origin"
})
.then(
function(response) {
// https://github.github.io/fetch/#Response
console.log(response.status); //=> number 100–599
console.log(response.statusText); //=> String
console.log(response.headers); //=> Headers
console.log(response.url); //=> String
return response.json();
},
function(error) {
error.message; //=> String
}
)
.then(function(responseAsJson) {
console.log(responseAsJson);
// {
// "id": 1, // новое поле, потому что пользователь успешно создан
// "username": "evgenyrodionov",
// "email": "[email protected]",
// "age": 24
// }
});
Окей, запрос понятен, вроде даже ответ как-то обрабатываем в функции, которую передаём в .then
. Кстати, а что за .then
?
На этом моменте мы узнаем интересный аспект: бывают синхронное и асинхронное программирование. К плаванью отношения не имеют.
Синхронное программирование это когда интерпретатор код выполняет строчка за строчкой:
const x = 1;
console.log(x); // 1
В асинхронном программировании мы не знаем, когда выполнится наша функция: то ли сразу же, то ли через 10 мс, то ли через 50 секунд, то ли через 20 минут. И как быть? Как с этим работать?
Существуют три способа.
Коллбэки (callbacks) это простая функция, которая вызывается, когда выполнится функция, которую вы вызвали.
Вы знали, что setState()
асинхронный? Если вы попробуете обратиться к стейту на следующей строчке после вызова setState()
, вы можете удивиться, когда увидите старые данные.
import React from "react";
class Counter extends React.Component {
state = {
counter: 0
};
handleClick = () => {
this.setState({ counter: this.state.counter + 1 });
console.log(this.state); // 0
};
render() {
return (
<React.Fragment>
<pre>{JSON.stringify(this.state, null, 2)}</pre>
<button onClick={this.handleClick}>+</button>
</React.Fragment>
);
}
}
iframe: https://codesandbox.io/embed/0mqo1ol5yl
Возможно, вы и не увидите этого: смысл ведь не в том, что запрос выполняется с задержкой, а в том, что неизвестно время получения результата.
Почему setState
асинхронный? Потому что бывает, что Реакт накапливает вызовы и потом разом обновляет стейт: так производительнее.
Как с этим работать? Обращайтесь к стейту в коллбеке.
this.setState({ counter: this.state.counter + 1 }, function() {
console.log(this.state); // 1
});
Коллбек это функция, которая передаётся аргументом и вызывается, когда код отработан. Пример:
function multiplyNumber(number, cb) {
const multiplied = number * number;
cb(multiplied);
}
console.log(multiplyNumber(2)); // undefined, потому что нет return в функции
multiplyNumber(2, function(result) {
console.log(result); // 4
});
Промисы (Promises) это обещания. Чисто технически, это те же коллбеки, но на стероидах: вместо аргумента мы используем методы .then()
и .catch()
у функции-промиса. Фетч работает на промисах. В методы мы передаём функцию.
fetch(url, params)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
В 2017 году в Экмаскрипт (стандарт, на котором работает Джаваскрипт) привезли новую фичу: async-await
— способ писать в синхронном стиле асинхронный код.
Асинк-эвейты работают вместо промисов в том числе.
const response = await fetch(url, params);
console.log(response);
А как работать с ошибками? Через конструкцию try..catch
:
try {
const response = await fetch(url, params);
console.log(response);
} catch (error) {
console.log(error);
}
Всегда обрабатывайте ошибки! Даже если знаете, что придут "чистые" данные: я так понадеялся на Телеграм и однажды свалил сайт на полчаса, потому что Телеграм стал присылать ошибку, которую я не обрабатывал — из-за этого падал весь бэкэнд.
Когда он перезапускался, то заново пытался выполнить и терпел неудачу, снова падая. С тех пор у меня весь код в try..catch
.
Резюмируя: для ХТТП-запросов мы используем Фетч, который работает асинхронно.
В асинхронном программировании мы не знаем, когда придёт результат, поэтому пишем функцию, которая вызовется, когда он всё-таки придёт.
Для асинхронного программирования есть три подхода в Джс: коллбэки, промисы и асинк-эвейт. Чтобы не было проблем и не бегать с горящей жопой, всегда нужно ловить ошибки и их обрабатывать: либо для себя выводить, либо использовать сервис тип Сентри или Багснега, либо показать ошибку пользователю.
Кстати, асинхронное программирование нужно, чтобы не блокировать основной поток исполнения кода: представьте, как бы вас бесил интерфейс, если бы он замерзал (даже скроллить нельзя!) пока не получил ответ или не обновил стейт. Ужасно же.
Сегодня мы прошлись по устройству ХТТП, узнали про асинхронное программирование, коллбэки, промисы и асинк-эвейты и Фетч, который работает на последних двух подходах.
И помните: всегда обрабатывайте ошибки. Это чертовски важно, хоть и рутинно сложно. Лучше пользователь увидит непонятное "произошла ошибка", чем не поймёт что произошло. А ещё лучше, если он узнает на чьей она стороне и что он может с этим сделать.