Artem Gorev

Мой дорогой дневничок


Когда появились Promise’ы в JavaScript, на первый взгляд они выглядели, как панацея в мире асинхронного программирования. Лекарство от Callback’ов. Но как только начинаешь использовать их, приходит понимание, что код не становится сильно легче читать, появляются странные цепочки вызовов, в которых нужно следить, что пришло, что ушло, кто выкинул ошибку, кто обработал ее. Все вроде бы понятно, но однотипного кода появлялось довольно много вокруг Promise’ов.

Promise появились в ES2015, но уже через пару лет появились async/await в ES2017. За достаточно небольшой промежуток времени разработчики осознали, что Promise не способны решить все проблемы асинхронного программирования.

Что это такое async/await?

Рассмотрим небольшой пример

const delay = (timeout) => {
	return new Promise(resolve => {
		setTimeout(() => resolve(), timeout);
	});
};

delay()
.then(() => {
	console.log(1);
})
.catch((error) => {
	console.log(error);
});

Теперь перепишем его с использованием async/await. Следует сказать, что async/await основаны на Promise’ах, поэтому наша функция delay останется неизменна.

const doSomethingAsync = async () => {
	try {
		await delay(1000);
		console.log(1);
  } catch ( error ) {
		console.log(error);
  }
}; 

Наш второй пример делает все то же самое что и первый. Только он написан с использованием async/await. Он стал больше похож на обыкновенный синхронный код, который мы чаще всего пишем. Согласитесь такой вариант проще воспринимать.

Подробности async/await

Как вы уже могли заметить, чтобы использовать await, функция должна иметь вначале ключевое слово async. Это означает, что результат выполнения функции будет Promise. Приведем разные способы объявления функций с использованием async.

async function myFirstAsyncFunction() {}
const myFX = async () => {};
class A { 
  static async s() {};
  async m() {};
} 

Все эти функции и методы будут возвращать Promise, даже если мы ничего не будем возвращать из него. Когда return не вызывается, функция возвращает undefined.

Чтобы обработать исключения которые могут возникать в результате использования Promise, больше не нужно вызывать над ним метод .catch. Нужно использовать уже привычный нам try… catch.

Если вы попробуете использовать await без async, то получите синтаксическую ошибку. Можно вызывать await над обычными функциями и даже данными, никакого потайного смысла в таких действиях нету, но и никакой ошибки в этом случае вы не получите.

Давайте рассмотрим пример выполнения сетевого запроса с использованием fetch. И преобразуем это пример с использованием async/await.

const data = { username: 'example' };
fetch('https://example.com/profile', {
  method: 'POST', // or 'PUT'
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
})
.then((response) => response.json())
.then((data) => console.log('Success:', data))
.catch((error) => console.error('Error:', error));

После асинкаватизации:

(async () => {
	try {
		const input = { username: 'example' };
		const response = await fetch('https://example.com/profile', {
			method: 'POST', // or 'PUT'
			headers: {
				'Content-Type': 'application/json',
			},
			body: JSON.stringify(input),
		});
		const data = await response.json();
		console.log('Success:', data);
	} catch (error) {
		console.error('Error:', error)
	}
})();

Вы можете сравнить два примера кода записанного с помощью Promise и async/await. Сразу отмечу отлаживать код с использованием async/await намного удобней, потому что он выглядит всего лишь как синхронный код.
Чтобы код заработал, он специально обрамлен в Immediately Invoked Function Expression (IIFE). В так сказать функцию которая как только была объявлена сразу же ее вызвали. Такие приемы мы будем часто встречать при работе с асинхронным кодом.

Async/Await и Циклы

Как только в ваши руки попадает такой прекрасный инструмент async/await, то вы его начинаете применять везде. Рассмотрим случай в котором async/await лучше не использовать.

Допустим у вас потребность удалить все записи из хранилища. Удаление каждого записи является асинхронным действием. Впервые я решил подобную задачу с помощью следующего кода:

let storage = [1,2,3,4, 5];
(async () => {
  async function remove(id) { console.log(id) }
  for( let i of storage) 
    await remove(i);
})();

Но если вы его выполните то на терминале будут выведены цифры от 1 до 5 по порядку. Можно переписать его в более эффективный код.

let storage = [1,2,3,4, 5];
(async () => {
  await Promise.all(storage.map( i => remove(i)));
})();

Получилось меньше кода, который выполнится параллельно. В результате эффективность решения данной задачи возрастет.