Решаем популярные задачи с асинхронным кодом на JavaScript: часть вторая

Отредактировано

Продолжаем разбирать задачи с асинхронным кодом в JS. В этой статье рассмотрим порядок попадания задач в Event Loop и оптимизацию кода.

6К открытий13К показов

В первой части текста мы вспомнили, как устроен цикл событий и разобрали несколько простых задач на асинхронное программирование на JS. В этой статье преподаватель Elbrus Bootcamp Денис Образцов разберёт более сложные примеры на порядок попадания задач в Event Loop и оптимизацию кода.

Задача первая

			let a;let p4 = new Promise(function (resolve) { console.log('TEST A1', a); a = 25; setTimeout(() => { console.log('TEST A2', a); resolve(a); }, 100);});setTimeout(function timeout() { a = 10; console.log('TEST A3', a);}, 100);p4.then(function (b) { console.log('TEST A4', a);});console.log('TEST A5', a);
		

Разберём задачу подробно. В первой строке объявляется переменная без присвоения значения, после которой создаётся promise и вывод в консоль значения a. Все перечисленные строки — это синхронный код, поскольку сам по себе promise не является асинхронным.

После этого ей присваивается значение: a = 25. Следующим шагом появляется таймер setTimeout, внутри которого вывод значения переменной в консоль и функция resolve, в которую вкладывается переменная ‘a’ для успешного исполнения promise c задержкой в 100 миллисекунд.

Если предыдущий таймер находился внутри promise, то второй setTimeout написан как отдельная функция. Здесь переменной ‘a’ присваивается новое значение, которое выводится в консоль с задержкой в 100 миллисекунд. На последних трёх строчках — обработка promise через then, которая завершается выводом консоли. Очередность вывода и переменная ‘a’ будут выглядеть следующим образом:

			let a;let p4 = new Promise(function (resolve) { console.log('TEST A1', a); // 1)синхронно, a = undefined a = 25; setTimeout(() => { console.log('TEST A2', a); // 3)асинхронно, a = 25 resolve(a); }, 100);});setTimeout(function timeout() { a = 10; console.log('TEST A3', a); // 5)асинхронно, a = 10}, 100);p4.then(function (b) { console.log('TEST A4', a); // 4)асинхронно, a = 25});console.log('TEST A5', a); // 2)синхронно, a = 25
		

С точки зрения очерёдности самая запутанная часть задачи — promise, внутри которого находится setTimeout. Базово таймер относится к макрозадачам, которые попадают в Call Stack последними и выполняются в последнюю очередь. Поскольку в этой задаче он находится внутри promise — микрозадачи, которая выполняется раньше, — в вывод он попадёт третьим. Сразу после того, как отработал весь синхронный код.

Если говорить о значениях, которые выводятся в консоль, то в первой строке будет undefined, поскольку никакое значение на этом этапе переменной не присвоено. Это произойдёт только в следующей строке. Консоль, которая выполняется во вторую очередь, выдаст значение 25, так как она расположена в конце кода, и к моменту её появления значение переменной уже присвоено. В следующих выводах в консоль выводится значение переменной.

Решаем популярные задачи с асинхронным кодом на JavaScript: часть вторая 1

Больше задач и историй студентов — в нашем Telegram-канале @Elbrus Bootcamp

Задача вторая

			// todo Объяснить код, рассказать какие консоли и в какой последовательности будут, а затем предложить более оптимальное решениеfunction resolveAfter2Seconds(x) { console.log(`Какой Х пришёл -> ${x}`) return new Promise(resolve => { setTimeout(() => { resolve(x); // }, 5000); });}async function add1(x) { console.log('add1 Hello') const a = await resolveAfter2Seconds(20); const b = await resolveAfter2Seconds(30); console.log('add1 Bye') return x + a + b;}add1(10).then(console.log);
		

В этой задаче появляется функция async. Прежде чем приступить к разбору, вспомним, для чего она нужна.

Функция async позволяет работать с асинхронным кодом так, будто он синхронный. Синхронный код не работает с асинхронным, но в пределах функции async возможно сделать вид, что так можно. Для этой задачи важно отметить, что любая async-функция сразу же возвращает promise. В паре с async идёт ключевое слово await, которое буквально означает «дождись». Сама по себе async-функция, как и promise — не является асинхронной.

Перейдём к разбору кода. В первой строке находится функция resolveAfter2Seconds, которая принимает аргумент x, выводит его значение в консоль и возвращает его в promise с задержкой в пять секунд.

Далее следует функция async. Внутри неё — синхронная консоль, за которой следуют две асинхронные функции, которые «дожидаются» своего выполнения через await. Результат их поочередного выполнения попадает в promise, а аргумент x приобретает значение сначала ‘a’, а затем — ‘b’. Затем выводится результат Bye.

В последней строчке функции указан вывод результата. Так как любая async возвращает promise, в return не удастся получить конкретного результата. Вместо него мы получим promise pending — ожидание. Then в последней строчке — обработчик этого pending.

В результате порядок выполнения будет следующим:

			function resolveAfter2Seconds(x) {  console.log(`Какой Х пришёл -> ${x}`) // 2) Какой Х пришёл -> 20 undefined 3) Какой Х пришёл -> 30  return new Promise(resolve => {    setTimeout(() => {      resolve(x); //    }, 5000);  });}async function add1(x) {  console.log('add1 Hello') // 1)add1 Hello   const a = await resolveAfter2Seconds(20);  const b = await resolveAfter2Seconds(30);  console.log('add1 Bye') // 4)add1 Bye  return x + a + b;}add1(10).then(console.log); // 5)60
		

Этот код работает медленно: чем больше внутри async-функции add1 вызовов функций resolveAfter2Seconds с ожиданием (await), тем больше времени займёт его выполнение. Если вызовов два, то он будет выполняться 10 секунд. Если их будет 10, ждать придётся почти минуту.

Попробуем переписать код и сократить время ожидания. Поставим все вызовы в очередь без await, чтобы они выполнялись все одновременно за пять секунд:

			async function add2(x) { console.log('add2 Hello') const p_a = resolveAfter2Seconds(200); const p_b = resolveAfter2Seconds(300); const p_c = resolveAfter2Seconds(100); const p_d = resolveAfter2Seconds(999); console.log('add2 Bye') return x + await p_a + await p_b + await p_c + await p_d;}add2(400).then(console.log);
		

Теперь мы дожидаемся выполнения всех функций уже в конце и только после этого показываем конечный результат. В результате оптимизации ждать придётся не 20 секунд, а всего пять.

Реклама ООО “Эльбрус Буткемп”

Следите за новыми постами
Следите за новыми постами по любимым темам
6К открытий13К показов