Цикл javascript: Циклы while и for

Содержание

ОПЕРАТОРЫ ЦИКЛА WHILE, DO…WHILE — Седьмой урок JavaScript

Главная — Уроки — Уроки JavaScript

  • конвертируем арифметическую прогрессию;
  • таблица умножения: for внутри while;
  • таблица умножения в «слоёном» while;
  • оператор do…while

Оператор while

Даже если Вы ещё никогда не программировали, то наверняка работали в разных программах просто как пользователь. И знаете, что одного и того же результата иногда можно достичь разными способами.

То же и в программировании. С помощью цикла while можно делать всё то же, что и с помощью цикла for. Что-то удобнее делать в одном цикле, что-то — в другом. Что-то быстрее в одном, что-то безопаснее в другом. Кто-то привык к одному, кто-то — к другому.

Чтобы Вам было понятнее, мы будем использовать те же самые примеры, но в новой «аранжировке».

While означает «в то время как» или «до тех пор, пока».

В то время как имеется условие и до тех пор, пока оно существует, выполняется код.

Синтаксис до боли знакомый:

while (условие) {код}

Попробуем «проиграть» в этом цикле наш первый пример: сумму чисел от 1 до 10. Вот как выглядят оба кода:

В цикле for значение счётчика i задавалось в заголовке цикла. В цикле while оно не задаётся, мы задаём его при объявлении переменной.

Шаг здесь переносится в самый конец. То есть мы задаём условие, назначаем на него действие и только потом указываем шаг для его выполнения.

Расширенный вариант, с выводом всех значений и блоком if для разделителей, попробуйте конвертировать сами. А потом взгляните на ответ.

А теперь немножко помучаемся с таблицей умножения.

Здесь у нас были вложенные циклы. Цикл while

тоже можно вкладывать, но мы наткнёмся на один нюанс, связанный с последовательностью действий и сохранением данных в переменной, и придётся немножко обмануть программу.

А сначала попробуем комбинированный вариант: внешний цикл сконвертируем в while, а внутренний оставим for.

Вот наш внешний цикл, выводящий <tr>.

var i = 2, j; // Сразу объявим переменную и для внутреннего цикла. // Назначать не будем, так как внутренний цикл у нас FOR. document.write(««) while (i «) // Здесь будет // вложенный цикл. document.write(««) i++ } document.write(«
«)

Теперь просто скопируем в отведённое место вложенный цикл for:

var i = 2, j; document.write(«
«) while (i «) for (j = 2; j » + j + «×» + i + «=» + (i * j) + «»)} document.write(««) i++ } document.write(«
«)

Теперь разберёмся с проблемой вложенных циклов while. Сейчас я приведу неправильный скрипт, и попробуем понять, почему он неправильный и как его исправить. Это будет неплохая разминка для мозгов.

var i = 2, j = 2; document.write(««) while (i «) while (j » + j + «×» + i + «=» + (i * j) + «») j++ } document.write(««) i++ } document.write(«
«)

Можете скопировать и запустить. Вы увидите заполненную первую строку и какую-то белую полоску под ней. Эта полоска — результат сгенерированных пустых <tr>. То есть внешний цикл честно отрабатывает до конца, а вложенный виснет на первом круге.

Вопрос: чему равна переменная j после первого прохождения большого цикла?

Ответ: она равна 10.

Вопрос: чему она должна быть равна к началу прохождения второго витка?

Ответ: 2, как задано.

Вопрос: как её сбросить?..

Давайте подумаем.

Есть оператор break, который мы использовали в switch. Здесь он тоже используется (потом мы о нём специально поговорим). Но сразу скажу, нашей беде он не поможет. С его помощью можно только поменять циклы (то есть будет выводиться только один вертикальный столбец).

Внимание. Думаем. Вникаем. Нам нужна переменная, которая бы содержала неизменную двойку. И нам нужна переменная, которая бы многократно проходила цикл от этой двойки. А что нам мешает создать ещё одну переменную, допустим,

k, приравнять её к j и запустить в цикл? Да ничего, разве что тормоза в голове.

Смотрите, она (k) пройдёт цикл, прирастая до 10, а в следующем витке вновь приравняется к неизменной j и снова пройдёт тот же цикл:

var i = 2, j = 2, k; document.write(««) while (i «) k = j while (k » + k + «×» + i + «=» + (i * k) + «») k++ } document.write(««) i++ } document.write(«
«)

Вывод: лучше, всё-таки, использовать for. Но чтобы это понять, надо попробовать и while.

А вот работающий скрипт без дополнительной переменной:

var i = 2, j; // обратите внимание на неназначенную j (А.Ф.) document.write(«
«) while (i «) j = 2 // и только здесь j получает значение (А.Ф.) while (j » + j + «×» + i + «=» + (i * j) + «») j++ } document.write(«») i++ } document.write(«
«)

Оператор do…while

Этот оператор очень похож на предыдущий. Различие, образно говоря, такое: если while сначала подумает, а потом сделает, то do…while сначала сделает, а потом подумает.

Если оператор while не найдёт нужного условия при проверке, то его код вообще не будет выпонняться. А код оператора do…while хотя бы один раз выполняется всегда. А выполнение (или невыполнение) дальнейшего цикла уже зависит от проверки условия.

Ниже приводится сравнение кодов выведения нашей арифметической прогрессии в этих двух операторах.

while

while (условие) проверяет {код} делает

do…while

do {код} делает while (условие) проверяет

А вот «аранжировка для do…while» нашей таблицы умножения (здесь тоже приходится использовать «лишнюю» переменную):

var i = 2, j = 2, k; document.write(««) do {document.write(««) k = j do {document.write(««) k++ } while (j «) i++ } while (i «) Вариант по И. Кретову без дополнительной переменной: var i = 2, j; document.write(«
» + k + «×» + i + «=» + (i * k) + «
«) do {document.write(««) j = 2 do {document.write(««) j++ } while (j «) i++ } while (i «)

В общем-то, мы закончили наше первое знакомство с операторами. Но есть ещё некоторые ключевые слова, вроде уже встречавшихся нам

break или default. Это, по сути дела, тоже маленькие вспомогательные операторы, и им мы посвятим следующий урок, прежде чем перейдём к массивам.

Итак, мы узнали:

как работают циклы while и do…while, как переписать код для из одного цикла в другой.

А также:

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

К следующему уроку

К списку уроков JavaScript

(© А. Фролов)


» + j + «×» + i + «=» + (i * j) + «

13 советов, как сделать код на JavaScript качественнее и быстрее

10 лет назад компания Amazon подсчитала, что 100 миллисекунд задержки стоили ей 1% выручки с продаж. Аналогичным образом в Google обнаружили, что дополнительные 500 миллисекунд на генерацию поисковых страниц сократили их трафик на 20%, на столько же урезав потенциальный доход от рекламы. Оказывается, скорость действительно решает всё. Мы публикуем перевод статьи британского разработчика Брета Кэмерона, в которой он дает 13 практических советов для увеличения скорости работы JavaScript-кода.

Меньше — лучше

Самый быстрый код — это код, который никогда не будет запущен

1. Удалите ненужные функции

Можно сразу приступить к оптимизации написанного кода, но часто наибольший прирост производительности достигается, если сделать шаг назад и спросить себя: нужен ли тот или иной сегмент на самом деле? Прежде чем перейти к оптимизации, спросите себя: должна ли ваша программа делать всё, что она делает? Необходимы ли все эти возможности, компоненты и функции? Если нет, удалите ненужное. Этот шаг невероятно важен для повышения скорости работы вашего кода, но про него легко забыть.

2. Избегайте ненужных шагов

Оценочный тест

Начав детально пересматривать код, спросите себя: каждый ли сделанный шаг необходим для успешного выполнения функции и достижения нужного результата? Например, не проходят ли данные через ненужные циклы? Следующий пример намеренно упрощен, но такое сложнее обнаружить в большой кодовой базе:

Даже в этом простом примере разница в производительности огромна: маленький работающий код быстрее большого неработающего. Хотя вышеописанную ошибку допускают немногие, в большом коде легко проглядеть лишние шаги, если нужный результат достигается. Избегайте их.

Реже — лучше

Если вы не можете удалить фрагмент кода, постарайтесь выполнять его реже 

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

3. Циклы должны завершаться как можно раньше

Оценочный тест

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

Или, если нужно произвести операции с определенными элементами цикла, вы можете пропустить другие операции с помощью оператора continue, который останавливает выполнение операторов в текущей итерации и немедленно переходит к следующей:

Стоит также помнить, что вложенные циклы можно разрывать с помощью меток. Это позволяет привязать оператор break или continue к определенному циклу:

4. Предвычисляйте, если возможно

Оценочный тест

Возьмем следующую функцию, в нашей программе мы хотим её вызвать несколько раз:

Проблема этого кода в том, что каждый раз, когда мы вызываем функцию whichSideOfTheForce, мы создаем новый объект. При каждом вызове функции под наши массивы без необходимости выделяется память. Учитывая, что «светлые» и «темные» значения статичны, лучшим решением было бы объявить эти переменные один раз, а затем ссылаться на них при вызове whichSideOfTheForce. Мы могли бы определить наши переменные в глобальной области видимости, но тогда они могли бы изменяться за пределами нашей функции. Лучшее решение — задействовать замыкание, чтобы функция вернула своё значение:

Теперь массивы «света» и «тьмы» будут создаваться всего один раз. То же самое относится к вложенным функциям. Например:

Каждый раз, когда мы запускаем doSomething, вложенная функция создается с нуля. Замыкание поможет и здесь. Если мы возвращаем функцию, doSomethingElse остается закрытой, но создается всего один раз:

5. Минимизируйте количество операций в коде

Оценочный тест

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

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

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

Ключевой момент — обеспечить выполнение операций в наилучшем порядке.

6. Изучите О-нотацию 

Знакомство с О-нотацией — один из лучших способов понять, почему одни функции исполняются быстрее и занимают меньше места в памяти, чем другие. Например, с помощью О-нотацией можно понять, почему бинарный поиск — один из самых эффективных алгоритмов поиска, а быстрая сортировка (quicksort) — один из самых эффективных алгоритмов сортировки данных.

По сути, О-нотация помогает применять и понимать описанные в этой статье способы оптимизации. Это глубокая тема, поэтому, если вам интересно узнать больше, я рекомендую прочитать мою статью об О-нотации или статью, где я разбираю четыре варианта решения задач на собеседовании в Google в контексте затраченного времени и пространственной сложности алгоритма.

Ускоряйте исполнение

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

7. Используйте встроенные инструменты

Оценочный тест

Для тех, кто имеет опыт работы с компиляторами и низкоуровневыми языками, этот момент может показаться очевидным. Если нужные вам инструменты уже содержатся в самом JavaScript «из коробки», используйте именно их.

Код компилятора оптимизирован с точки зрения производительности конкретных методов и типов объектов. Кроме того, базовым языком для JavaScript служит C++. Если у вас не очень специфичный случай, вероятность того, что ваша собственная реализация JavaScript превзойдет существующие методы, крайне мала.

Чтобы проверить, создадим собственную JavaScript-реализацию Array.prototype.map:

Теперь давайте создадим массив из 100 случайных чисел от 1 до 100:

Даже если мы хотим выполнить простую операцию, например, умножить каждое целое число в массиве на два, мы всё равно увидим разницу в производительности:

В моих тестах новая функция map JavaScript оказалась примерно на 65% медленнее, чем Array. prototype.map. Чтобы посмотреть исходный код реализации Array.prototype.map движка V8, кликните сюда. А чтобы провести тесты самому, кликните по ссылке на оценочный тест.

8. Используйте объекты, наиболее подходящие для конкретной задачи

Что эффективнее:

— добавлять значения в коллекцию Set или в массив с помощью push()?

— добавлять записи в коллекцию Map или в обычный объект?

Как и в предыдущих пунктах, наилучшая производительность достигается за счет выбора соответствующего объекта для решения текущей задачи. Встроенные в JavaScript объекты хорошо дополняют примитивные типы данных: числа, строки, функции, объекты и так далее. При использовании в верном контексте многие из этих менее распространенных объектов могут дать значительные преимущества в производительности.

В других статьях я писал о том, что использовать Set может быть быстрее, чем Array, а Map — чем регулярные объекты. Set и Map — это коллекции, которые используют ключи, они могу пригодиться, если вы регулярно добавляете или удаляете значения. Познакомьтесь со встроенными типами объектов и всегда используйте лучший инструмент из доступных, так вы сможете сделать код быстрее.

9. Не забудьте про память

Будучи высокоуровневым языком, JavaScript учитывает нюансы более низких уровней. Один из таких нюансов — управление памятью. JavaScript использует систему сбора «мусора» для высвобождения данных из памяти (если только явно не прописать в коде, что эти данные всё ещё нужны).

Хотя управление памятью в JavaScript выполняется автоматически, это не означает, что оно безупречно. Есть дополнительные шаги, которые можно предпринять для управления памятью и снижения вероятности утечек памяти.

Например, у функций Set и Map есть так называемые слабые вариации (WeakSet и WeakMap). Они содержат «слабые» ссылки на объекты. Они не используют тип enumerable, но они предотвращают утечку памяти, так как позволяют отправлять в «мусор» не упомянутые в коде значения.

Вы также можете лучше контролировать распределение памяти, используя объекты TypedArray, представленные в обновлении JavaScript ES2017. Например, Int8Array может принимать значения от –128 до 127 и имеет размер всего в один байт. Стоит отметить, однако, что прирост производительности при использовании объектов TypedArray может быть очень мал: сравнение обычного массива и Uint32Array показывает небольшое улучшение производительности записи, но незначительное или нулевое улучшение производительности чтения (спасибо Крису Ху за оба теста).

Поняв низкоуровневый язык программирования, вы сможете быстрее и лучше писать код на JavaScript. Об этом я пишу подробнее в своей статье «Как C++ может помочь JavaScript-разработчикам».

10. Используйте мономорфные операции, если возможно

Оценочный тест 1: мономорфные операции vs полиморфные

Оценочный тест 2: один аргумент функции vs два

Если присвоить переменной a значение 2 с помощью const, то её можно считать полиморфной (ее можно изменить). Напротив, если мы будем непосредственно использовать числовое значение 2, то его можно считать мономорфным (его значение фиксировано).

Конечно, полезно присваивать переменным значения, если нам нужно использовать их несколько раз. Если вы используете переменную только один раз, немного быстрее будет, если не устанавливать переменную вовсе. Возьмем простую функцию умножения:

Если запустить функцию multiply(2, 3), она завершится примерно на 1% быстрее, чем если запустить такой код:

Это довольно мало. Но в большой кодовой базе подобные маленькие победы складываются в большую. Таким же образом использование аргументов в функциях дает гибкость в ущерб производительности. Опять же, аргументы — это неотъемлемая часть программирования. Но если они вам не нужны, вы получите преимущество в производительности, если не будете их использовать. Еще более быстрая версия нашей функции умножения может выглядеть так:

Как я отметил ранее, улучшение производительности невелико (в моих тестах около 2%). Но если такое улучшение может быть использовано многократно в большой кодовой базе, то стоит подумать об этом. Как правило, вводить аргументы стоит тогда, когда значение динамическоe, а переменные вводятся только тогда, когда будут использоваться несколько раз.

11. Избегайте оператора delete

Оценочный тест 1: удаление свойств из объекта vs неопределенные свойства

Оценочный тест 2: оператор delete vs Map.prototype.delete

Оператор delete используется для удаления содержания из объекта. Может показаться, что он вам необходим, но если вы можете обойтись без него, то сделайте это. В движке V8 есть паттерн скрытых классов, и delete лишает вас преимуществ этого паттерна, делая объект обычным и медленным. А операции с медленными объектами — всё верно — выполняются медленнее.

В зависимости от ваших потребностей можно определить нежелательное свойство как неопределенное и, возможно, этого будет достаточно:

Я видел в интернете предположения, что было бы быстрее создать копию первоначального объекта без определенных свойств, используя следующие функции:

Однако в моих тестах функция, описанная выше, и некоторые другие работали даже медленнее, чем оператор delete. Кроме того, такие функции менее удобочитаемые, чем delete obj.a или obj.a = undefined.

В поисках альтернативы подумайте, можно ли использовать Map вместо объекта, так как Map.prototype.delete работает быстрее, чем оператор delete.

Откладывайте исполнение

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

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

12. Используйте асинхронный код для предотвращения блокировки потока

По умолчанию JavaScript является однопоточным языком и запускает свой код синхронно, шаг за шагом. В большинстве случаев это работает хорошо, но если у нас есть события, которые занимают много времени, не стоит блокировать или задерживать исполнение более важного кода.

Решение можно найти через асинхронный код. Некоторые встроенные инструменты работают так по умолчанию (вроде fetch() или XMLHttpRequest()), но стоит также отметить, что любую синхронную функцию можно сделать асинхронной. Если у вас длительная (синхронная) операция, например, операция над каждым из элементов в большом массиве, то такой код можно сделать асинхронным, чтобы он не блокировал выполнение другого кода. Если вы новичок в асинхронном коде JavaScript, ознакомьтесь с моей статьей «Что обещает JavaScript».

Кроме того, многие модули (например, файловая система Node.js) имеют асинхронные и синхронные варианты некоторых функций (например, fs.writeFile() и fs.writeFileSync()). В обычных условиях придерживайтесь стандартного асинхронного подхода.

13. Используйте разделение кода

Если вы используете JavaScript на стороне клиента, ваши приоритеты должны быть сосредоточены в визуальном быстродействии. Главный оценочный критерий — это First Contentful Paint (FCP), время, за которое появляется первый полезный для пользователя контент в DOM-элементах.

Разделение кода — один из лучших способов улучшить ситуацию. Вместо того, чтобы подавать ваш JavaScript-код одним большим файлом, подумайте о том, чтобы разделить его на более мелкие фрагменты. Как вы будете разбивать код, зависит от того, используете ли вы один из фреймворков (React, Angular, Vue) или обходитесь стандартными средствами самого JavaScript. 

Tree shaking — связанная с описанным кодом тактика статического анализа всего кода и исключения того, что на самом деле не используется. Чтобы узнать больше, я рекомендую эту статью от Google. Не забывайте оптимизировать свой код!

Заключение

Тестирование — лучший способ проверить, удалось ли вам оптимизировать код. В этой статье я привожу примеры кода, используя https://jsperf.com, но можно проверять и меньшие сегменты коды здесь:

— http://jsben.ch

— https://jsbench.me

— Ваша собственная консоль, через функции console.time() и console.timeEnd().

Что касается проверки производительности веб-приложений, отличной отправной точкой являются разделы Chrome Dev Tools про сети и производительность. Также рекомендую расширение Lighthouse от Google.

Наконец, хочу сказать, что, хоть скорость и важна, хороший код должен быть не только быстрым. Удобочитаемость и легкость сопровождения также исключительно важны, вряд ли нужно чуть-чуть увеличивать скорость, если такое улучшение приводит к увеличению времени на поиск и исправление ошибок.

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

Почему разработчики JavaScript ненавидят циклы FOR? | Фернандо Доглио

Я имею в виду, кто не знает, верно?

Фото Дмитрия Вечорко на Unsplash

Циклы FOR в стиле C устарели, многословны для написания и требуют, чтобы вы также определяли дополнительную переменную и отслеживали ее на протяжении всего процесса.

С таким количеством доступных альтернатив, почему язык вообще должен их поддерживать? Почему нет предложения убрать цикл FOR?

Дело в том, дорогой ненавистник циклов FOR, что у каждой конструкции в нашем любимом языке есть место и причина быть там. Да, включая цикл FOR. Итак, давайте посмотрим, какие существуют альтернативы, их конкретные варианты использования, а также проанализируем, почему традиционный цикл FOR по-прежнему актуален.

Пойдем?

Так как JavaScript так любит нас и хочет, чтобы мы были счастливы и были наполнены вариантами, у нас есть два (или три, в зависимости от того, как вы на них смотрите) разных способа повторения одной и той же части логики несколько раз (кроме тот, кого ты ненавидишь, конечно).

Примечание от автора: обратите внимание, что мне пришлось несколько раз переписать приведенный выше абзац, так как я хотел, чтобы язык был как можно более общим. Мы увидим, почему через минуту.

Тем не менее, когда мы думаем о способах написания цикла FOR, который не включает версию, которую мы все так любим ненавидеть, мы приходим к следующим ответам.

Метод массивов .forEach

Да, это, вероятно, самая популярная версия, потому что мы, разработчики JS, любим функциональное программирование. Верно?

Нам нравится думать о себе как о повелителях кода и с отвращением смотреть свысока на другие циклы, когда мы используем метод .forEach .

В случае, если вы не слишком знакомы с ним (в таком случае: как вы смеете!), этот метод позволяет вам вызывать функцию для каждого элемента внутри массива. Упомянутая функция будет получать каждый элемент в качестве своего первого аргумента, и ваша логика может использовать его для всего, что вам нужно.

Что-то вроде этого:

Это отличный способ работы с элементами внутри массива. Однако у этого метода — и, если честно, у всех методов массива — есть одна серьезная «проблема», если ваша логика асинхронна: он не может с этим справиться.

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

Результат этого выполнения следующий: code, на самом деле является первым, который будет записан в консоль. Мы не получаем желаемого поведения, и это просто потому, что 9Метод 0027 forEach не будет ожидать завершения своей внутренней функции на каждой итерации. И поскольку это закрытый метод, мы не можем изменить его работу, не переписав его полностью.

Да, есть альтернативы, работающие со списками промисов, но здесь мы говорим о циклах FOR, так что давайте двигаться дальше.

Еще одна крошечная деталь этого метода заключается в том, что это метод массива, а это означает, что мы не можем использовать его, например, для перебора свойств объекта. Нет, если мы сначала не получим список свойств, например, с Object.getOwnPropertyNames или подобное, но это было бы мошенничеством. Итак, давайте посмотрим, что мы можем сделать и с этим.

Циклы for..on и for..in

Итак, эти два цикла очень похожи и могут рассматриваться как нечто среднее между тем, который мы все ненавидим, и циклом forEach . Почему? Потому что они обеспечивают более низкий уровень абстракции, чем их функциональный аналог, но не такой низкий уровень, как тот, кого нельзя называть.

Давайте посмотрим на них обоих:

Можете ли вы найти между ними разницу? На самом деле это просто, for. .in выполняет итерацию по индексам массива, и это то, что он присвоит нашей локальной переменной i . С другой стороны, вариант for..of имеет дело с фактическими значениями внутри массива.

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

«Но Фернандо, — слышу я, — они асинхронно-совместимы?». Хороший вопрос, дорогой ненавистник цикла, хороший вопрос. Давайте посмотрим:

Как видите, я использую ту же логику, что и раньше. Как вы думаете, какой здесь будет результат?

ДА! Теперь мы получаем ожидаемое поведение. И это не должно вызывать удивления, ведь здесь мы имеем дело с очень простой конструкцией, которая позволяет нам точно определить, что происходит между итерациями. Таким образом, ключ await работает так, как и следовало ожидать (скрытая «магия» вокруг него, как раньше, не работает).

Давайте также быстро проверим, что произойдет, если мы попытаемся использовать эти циклы с объектами вместо массивов:

О боже, они тоже работают с объектами!! Это означает, что они уже более мощные и универсальные, чем метод .forEach . Это прекрасно. Мы получили все, о чем просили, так зачем нам продолжать дальше?

Ну, есть две основные «проблемы», которые я еще не выделил в этих двух решениях:

  1. Мы всегда перебираем весь массив/объект. Например, мы не можем перебрать последние 10 элементов. Нет, если только мы не сделаем копию исходного массива с меньшим количеством значений.
  2. Мы всегда имели дело с массивами (или объектами в последнем примере). Но что, если у нас есть часть логики, которую нужно выполнить несколько раз, но она не зависит от списка элементов? Должны ли мы по-прежнему создавать фальшивый список только для того, чтобы использовать цикл? Это не кажется правильным, не так ли?

Как я упоминал ранее, когда я определял цикл FOR, именно по этой причине язык должен был быть общим. Я не хотел портить весь смысл низкоуровневого цикла FOR: гибкость, позволяющая делать все, что вы хотите, как бы вы этого ни хотели.

Давайте посмотрим на это.

Если вам понравилось то, что вы прочитали до сих пор, рассмотрите возможность подписаться на мою БЕСПЛАТНУЮ рассылку « Бред старого разработчика » и получайте регулярные советы об ИТ-индустрии прямо в свой почтовый ящик.

Синтаксис этого плохого мальчика следующий:

Цикл FOR настолько силен, как вы видите, это просто цикл, который будет выполняться до тех пор, пока не будет выполнено условие. Вот и все.

  • <ПЕРЕМЕННАЯ ИНИЦИАЛИЗАЦИЯ> 9Раздел 0028 обычно используется для инициализации внутреннего счетчика. Но вы также можете либо оставить его пустым, либо одновременно инициализировать несколько переменных. Ограничений нет. Смысл этого раздела в том, что он выполняется в начале цикла и никогда больше.
  • — это логическое выражение, которое сообщает циклу, когда следует остановиться. Это условие будет проверяться в начале каждой итерации. Если вы оставите его пустым, цикл будет работать вечно. И, как и любое логическое выражение в JS, вы можете сделать его настолько сложным или простым, насколько захотите.
  • <КОД ДЛЯ ВЫПОЛНЕНИЯ КАЖДОЙ ИТЕРАЦИИ> обычно зарезервирован для изменения значения переменной-счетчика, которая проверяется в разделе <КОНЕЦ УСЛОВИЯ> . Однако это всего лишь код, который вызывается самим циклом без какого-либо контроля над ним. Вы можете разместить здесь все, что угодно.

Обычно этот цикл имеет следующую форму:

И да, это правильное использование, но зачем себя ограничивать?

Посмотрите на эти 3 очень правильных и красивых цикла FOR. Первые два довольно просты. Следуя приведенному выше определению, вы можете в значительной степени понять их поведение.

А как насчет последнего? Что, по вашему мнению, является результатом этого цикла?

Он будет начинаться с i с 0, но x с 19, и на каждой итерации будет увеличиваться i , но также будет уменьшаться x , пока ни одно из i не станет меньше 10 x NOR больше, чем 5.

Уже догадались? Вот я тебе руку подам:

Да, это не то, что все ожидают. Но посмотрите на это так. К моменту i достигает 10, x все еще больше 5, поэтому цикл продолжается. Как только x достигает 4, цикл завершается, потому что к тому времени i уже равно 14. И поскольку проверки выполняются в начале каждого цикла, мы не можем увидеть, что эти последние два числа напечатаны.

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

Однако только потому, что вы МОЖЕТЕ злоупотреблять этим замечательным инструментом, если захотите, вас ничто не принуждает к этому.

Когда вы будете использовать эту версию цикла?

«Каждому свое»

Вы когда-нибудь слышали эту фразу? Это само определение того, что вам нужно сделать, пытаясь выбрать правильную версию цикла FOR.

Если вы имеете дело со списком элементов внутри массива, скорее всего, вы используете метод forEach (или любой из его функциональных вариантов).

Если, с другой стороны, вы хотите выполнить итерацию по объекту или, возможно, вы хотите выполнять некоторые асинхронные функции на каждой итерации. Рассмотрим вариации for..of и for..in — или, может быть, еще немного изучите обещания.

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

Просто помните слова дяди Бена:

С большой гибкостью приходит ответственность не писать малопонятный код, который нигде не комментируется.

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

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

На этом этапе я надеюсь, что убедил вас перестать ненавидеть инструменты более низкого уровня и особенно бедный старый цикл FOR, который был с нами с самого начала JavaScript.

А если серьезно, у каждой особенности языка есть цель, и совсем другое дело, относится ли эта цель к вашей повседневной жизни. Вместо этого постарайтесь не распространять ненависть по отношению к конструкции, которую, возможно — только возможно — вы не совсем понимаете.

Не строить веб-монолиты. Используйте Bit для создания и компоновки несвязанных программных компонентов — в ваших любимых средах, таких как React или Node. Создавайте масштабируемые и модульные приложения с мощными и приятными возможностями разработки.

Пригласите свою команду в Bit Cloud , чтобы вместе размещать и совместно работать над компонентами, а также ускорять, масштабировать и стандартизировать разработку в команде. Попробуйте компонуемых внешних интерфейсов с Design System или Micro Frontends или изучите компонуемых внутренних интерфейсов с серверными компонентами .

Попробуйте →

Как мы создаем микроинтерфейсы

Создание микроинтерфейсов для ускорения и масштабирования процесса веб-разработки.

blog.bitsrc.io

Как мы создаем систему проектирования компонентов

Создание системы проектирования с компонентами для стандартизации и масштабирования процесса разработки пользовательского интерфейса.

blog.bitsrc.io

Компонуемое предприятие: руководство

Чтобы работать в 2022 году, современное предприятие должно стать компонуемым.

blog.bitsrc.io

Как создать компонуемый блог

Создание блога с нуля требует довольно много времени. Есть ряд движущихся частей, которые собираются вместе, чтобы создать…

bit. cloud

Расширяемые компоненты пользовательского интерфейса

Недавно мне было поручено создать компонент карты пользователя для платформы bit.cloud. Мне также было поручено создать…

bit.cloud

Как работает JavaScript: Цикл событий и развитие асинхронного программирования + 5 способов улучшить кодирование с помощью async/await | by Александр Златков

Добро пожаловать в пост №4 из серии, посвященной изучению JavaScript и его строительных компонентов. В процессе определения и описания основных элементов мы также поделимся некоторыми практическими правилами, которые мы используем при создании SessionStack, приложения JavaScript, которое должно быть надежным и высокопроизводительным, чтобы оставаться конкурентоспособным.

Вы пропустили первые три главы? Вы можете найти их здесь:

  1. Обзор движка, среды выполнения и стека вызовов
  2. Внутри движка Google V8 + 5 советов по написанию оптимизированного кода
  3. Управление памятью + как справиться с 4 распространенными утечками памяти

На этот раз мы расширим наш первый пост, рассмотрев недостатки программирования в однопоточной среде и способы их преодоления с помощью цикла событий и async/await для создания потрясающих пользовательских интерфейсов JavaScript. По традиции в конце статьи мы дадим 5 советов, как писать более чистый код с помощью async/await 9.0005

Почему наличие одного потока является ограничением?

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

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

Пока у стека вызовов есть функции для выполнения, браузер не может ничего сделать — он заблокирован. Это означает, что браузер не может рендерить, он не может запускать какой-либо другой код, он просто завис. И здесь возникает проблема — пользовательский интерфейс вашего приложения больше не эффективен и не приятен.

Ваше приложение зависло .

В некоторых случаях это может быть не такой критической проблемой. Но эй - вот еще большая проблема. Как только ваш браузер начнет обрабатывать слишком много задач в стеке вызовов, он может перестать отвечать на запросы в течение длительного времени. В этот момент многие браузеры предпримут действия, выдав сообщение об ошибке, спрашивая, должны ли они закрыть страницу:

Это уродливо и полностью разрушает ваш UX:

Строительные блоки программы JavaScript

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

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

Давайте рассмотрим следующий пример:

Вы, наверное, знаете, что стандартные запросы Ajax не выполняются синхронно, а это означает, что во время выполнения кода функция ajax(..) еще не имеет никакого значения чтобы вернуться назад, чтобы быть назначенным переменной ответа.

Простой способ «ожидания» возврата результата асинхронной функцией — использовать функцию с именем обратный вызов:

Просто примечание: вы можете выполнять синхронных Ajax-запросов. Никогда, никогда не делай этого. Если вы сделаете синхронный запрос Ajax, пользовательский интерфейс вашего приложения JavaScript будет заблокирован — пользователь не сможет щелкать, вводить данные, перемещаться или прокручивать. Это предотвратит любое взаимодействие с пользователем. Это ужасная практика.

Вот как это выглядит, но, пожалуйста, никогда так не делайте — не портите сеть:

Мы использовали запрос Ajax просто в качестве примера. Любой фрагмент кода может выполняться асинхронно.

Это можно сделать с помощью функции setTimeout(callback, миллисекунды) . Что делает функция setTimeout , так это устанавливает событие (тайм-аут), которое произойдет позже. Давайте посмотрим:

Вывод в консоли будет следующим:

 первое 
третье
второе

Что такое Event Loop?

Мы начнем с довольно странного утверждения — несмотря на то, что асинхронный код JavaScript (например, setTimeout , который мы только что обсуждали) разрешен, до ES6 сам JavaScript фактически никогда не имел прямого встроенного понятия асинхронности. Движок JavaScript никогда не делал ничего, кроме выполнения одного фрагмента вашей программы в любой момент времени.

Чтобы узнать больше о том, как работают движки JavaScript (в частности, Google V8), ознакомьтесь с одной из наших предыдущих статей на эту тему.

Итак, кто говорит JS Engine выполнять фрагменты вашей программы? На самом деле JS Engine не работает изолированно — он работает внутри среды с хостингом , которая для большинства разработчиков является типичным веб-браузером или Node. js. На самом деле, в настоящее время JavaScript внедряется во все виды устройств, от роботов до лампочек. Каждое отдельное устройство представляет отдельный тип среды хостинга для JS Engine.

Общим знаменателем во всех средах является встроенный механизм, называемый циклом событий, который обрабатывает выполнение нескольких фрагментов вашей программы с течением времени, каждый раз вызывая JS Engine.

Это означает, что JS Engine — это просто среда выполнения по требованию для любого произвольного кода JS. Это окружающая среда, которая планирует события (выполнение кода JS).

Итак, например, когда ваша программа JavaScript делает Ajax-запрос для получения некоторых данных с сервера, вы настраиваете код «ответа» в функции («обратный вызов»), и JS Engine сообщает среде хостинга:
«Эй, я собираюсь приостановить выполнение на данный момент, но когда вы закончите с этим сетевым запросом, и у вас есть какие-то данные, пожалуйста, вызовите эту функцию обратно ».

Затем браузер настраивается на прослушивание ответа из сети, и когда ему есть что вам вернуть, он запланирует выполнение функции обратного вызова, вставив ее в цикл событий .

Давайте посмотрим на диаграмму ниже:

Подробнее о куче памяти и стеке вызовов можно прочитать в нашей предыдущей статье.

И что это за веб-API? По сути, это потоки, к которым вы не можете получить доступ, вы можете просто вызывать их. Это части браузера, в которых запускается параллелизм. Если вы разработчик Node.js, это C++ API.

Так что же такое цикл событий

после всего ?

Цикл обработки событий имеет одну простую задачу — контролировать стек вызовов и очередь обратного вызова. Если стек вызовов пуст, цикл событий возьмет первое событие из очереди и поместит его в стек вызовов, который эффективно его запустит.

Такая итерация называется тиком в цикле событий. Каждое событие — это просто обратный вызов функции.

Давайте «выполним» этот код и посмотрим, что произойдет:

  1. Состояние чистое. Консоль браузера чиста, а стек вызовов пуст.

2. console.log('Привет') добавлен в стек вызовов.

3. console.log('Привет') выполняется.

4. console.log('Привет') удален из стека вызовов.

5. setTimeout(function cb1() { ... }) добавляется в стек вызовов.

6. setTimeout(функция cb1() { ... }) выполняется. Браузер создает таймер как часть веб-API. Он будет обрабатывать обратный отсчет для вас.

7. Сам setTimeout(function cb1() { ... }) завершен и удален из стека вызовов.

8. console.log('Пока') добавлен в стек вызовов.

9. console.log('Пока') выполняется.

10. console.log('Пока') удален из стека вызовов.

11. По истечении как минимум 5000 мс таймер завершает работу и отправляет обратный вызов cb1 в очередь обратного вызова.

12. Цикл событий берет cb1 из очереди обратного вызова и помещает его в стек вызовов.

13. cb1 выполняется и добавляет console.log('cb1') в стек вызовов.

14. console.log('cb1') выполняется.

15. console.log('cb1') удален из стека вызовов.

16. cb1 удален из стека вызовов.

Краткий обзор:

Интересно отметить, что ES6 определяет, как должен работать цикл событий, а это означает, что технически это находится в рамках обязанностей JS-движка, который больше не играет роль только среды размещения. Одной из основных причин этого изменения является введение промисов в ES6, потому что последние требуют доступа к прямому, детализированному контролю над планированием операций в очереди цикла событий (мы обсудим их более подробно позже).

Как работает setTimeout(…)

Важно отметить, что setTimeout(…) автоматически не помещает ваш обратный вызов в очередь цикла обработки событий. Он устанавливает таймер. Когда таймер истекает, среда помещает ваш обратный вызов в цикл событий, чтобы какой-нибудь будущий тик подхватил его и выполнил. Взгляните на этот код:

Это не означает, что myCallback будет выполнено через 1000 мс, а скорее то, что через 1000 мс myCallback будет добавлено в очередь цикла событий. Однако в очереди могут быть другие события, которые были добавлены ранее — вашему обратному вызову придется подождать.

Существует немало статей и руководств по началу работы с асинхронным кодом в JavaScript, в которых предлагается выполнить setTimeout(callback, 0) . Что ж, теперь вы знаете, что делает цикл событий и как работает setTimeout: вызов setTimeout с 0 в качестве второго аргумента просто откладывает обратный вызов до тех пор, пока стек вызовов не будет очищен.

Взгляните на следующий код:

Хотя время ожидания установлено на 0 мс, результат в консоли браузера будет следующим:

 Привет 
Пока
обратный вызов

Что такое вакансии в ES6?

В ES6 была введена новая концепция под названием «Очередь заданий». Это слой поверх очереди Event Loop. Скорее всего, вы столкнетесь с этим, когда имеете дело с асинхронным поведением промисов (о них мы тоже поговорим).

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

Представьте себе это так: очередь заданий — это очередь, которая присоединяется к концу каждого тика в очереди цикла событий. Некоторые асинхронные действия, которые могут выполняться во время такта цикла событий, не приведут к добавлению нового события в очередь цикла событий, а вместо этого добавят элемент (также известный как задание) в конец очереди заданий текущего такта.

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

Задание также может привести к добавлению дополнительных заданий в конец одной и той же очереди. Теоретически возможно, что «цикл» задания (задание, которое продолжает добавлять другие задания и т. д.) может вращаться бесконечно, тем самым лишая программу необходимых ресурсов, необходимых для перехода к следующему такту цикла событий. Концептуально это было бы похоже на простое выражение длинного или бесконечного цикла (например, в то время как (правда) ..) в вашем коде.

Задания похожи на «хак» setTimeout(callback, 0) , но реализованы таким образом, что они вводят гораздо более четко определенный и гарантированный порядок: позже, но как можно скорее.

Обратные вызовы

Как вы уже знаете, обратные вызовы на сегодняшний день являются наиболее распространенным способом выражения и управления асинхронностью в программах JavaScript. Действительно, обратный вызов является наиболее фундаментальным асинхронным шаблоном в языке JavaScript. Бесчисленные JS-программы, даже очень сложные и сложные, были написаны без использования какой-либо другой асинхронной основы, кроме обратного вызова.

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

В следующей главе мы подробно рассмотрим пару этих абстракций, чтобы показать, почему необходимы и даже рекомендуются более сложные асинхронные шаблоны (которые будут обсуждаться в последующих статьях).

Вложенные обратные вызовы

Посмотрите на следующий код:

У нас есть цепочка из трех вложенных друг в друга функций, каждая из которых представляет собой шаг в асинхронной последовательности.

Такой код часто называют «ад обратных вызовов». Но «ад обратных вызовов» на самом деле не имеет почти ничего общего с вложенностью/отступом. Это гораздо более глубокая проблема.

Сначала мы ждем события «щелчок», затем ждем срабатывания таймера, затем ждем ответа Ajax, после чего все может повториться снова.

На первый взгляд может показаться, что этот код естественным образом сопоставляет свою асинхронность с последовательными шагами, такими как:

Затем у нас есть:

Затем у нас есть:

И, наконец:

Итак, такой последовательный способ выражения вашей асинхронности код кажется намного более естественным, не так ли? Должен же быть такой путь?

Promises

Взгляните на следующий код:

Все очень просто: он суммирует значения x и y и выводит результат на консоль. Что если, однако, значение x или y отсутствовали и их еще нужно было определить? Скажем, нам нужно получить значения x и y с сервера, прежде чем их можно будет использовать в выражении. Давайте представим, что у нас есть функция loadX и loadY , которые соответственно загружают с сервера значения x и y . Затем представьте, что у нас есть функция sum , которая суммирует значения x и y после их загрузки.

Это могло бы выглядеть так (весьма некрасиво, не так ли):

Здесь есть кое-что очень важное — в этом фрагменте мы трактовали x и y как будущих значений, и мы выразили операцию sum(…) которому (извне) было все равно, доступны ли x или y или оба сразу или нет.

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

Promise Value

Давайте кратко рассмотрим, как мы можем выразить пример x + y с помощью промисов:

В этом фрагменте есть два слоя промисов.

fetchX() и fetchY() вызываются напрямую, а возвращаемые ими значения (обещания!) передаются в sum(...) . Основные ценности, которые представляют эти обещания, могут быть готовы теперь или позже , но каждое обещание нормализует свое поведение, чтобы оно было одинаковым независимо от того. Мы рассуждаем о значениях x и y независимым от времени способом. Это будущих значений , точка.

Второй уровень — это промис, который sum(...) создает
(через Promise.all([ ... ]) ) и возвращает результат, который мы ждем, вызывая then(...) . Когда операция sum(...) завершается, наше будущее значение sum равно 9.0018 готов и мы можем его распечатать. Мы скрываем логику ожидания будущих значений x и y внутри sum(...) .

Примечание : Внутри sum(…) вызов Promise.all([ … ]) создает обещание (которое ожидает разрешения promiseX и promiseY ). Связанный вызов .then(...) создает другое обещание, которое возвращает
values[0] + values[1] 9Строка 0028 сразу разрешается (с результатом сложения). Таким образом, вызов then(. ..) , который мы связываем в конце вызова sum(...) — в конце фрагмента — фактически работает с этим вторым возвращенным обещанием, а не с первым. создано Promise.all([ ... ]) . Кроме того, хотя мы не связываем конец этого второго then(...) , он также создал другое обещание, если бы мы решили его соблюдать/использовать. Эта цепочка промисов будет объяснена более подробно позже в этой главе.

С промисами вызов then(...) фактически может выполнять две функции, первую для выполнения (как показано ранее), а вторую для отклонения:

Если что-то пошло не так при получении x или y , или что-то пошло не так во время добавления, обещание, что sum(...) вернет, будет отклонено, а второй обработчик ошибок обратного вызова передаст then(...) и получит значение отклонения из обещать.

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

Более того, как только обещание разрешено, оно остается таким навсегда — оно становится неизменяемым значением в этот момент — и затем может наблюдаться столько раз, сколько необходимо.

Действительно полезно, что вы можете на самом деле связывать обещания:

Вызов delay(2000) создает обещание, которое будет выполнено через 2000 мс, а затем мы возвращаем его из первого обратного вызова then(...) выполнения, который вызывает второе обещание then(...) ждать этого обещания 2000 мс.

Примечание : поскольку промис является внешне неизменным после разрешения, теперь можно безопасно передавать это значение любой стороне, зная, что оно не может быть изменено случайно или злонамеренно. Это особенно верно в отношении нескольких сторон, наблюдающих за разрешением Обещания. Одна сторона не может повлиять на способность другой стороны соблюдать разрешение Обещания. Неизменяемость может показаться академической темой, но на самом деле это один из самых фундаментальных и важных аспектов дизайна промисов, и его нельзя просто так упускать из виду.

Обещать или не обещать?

Важная деталь промисов — точно знать, является ли какое-то значение реальным промисом или нет. Другими словами, будет ли это значение вести себя как обещание?

Мы знаем, что промисы строятся с помощью синтаксиса new Promise(…) , и вы можете подумать, что p instanceof Promise будет достаточной проверкой. Ну, не совсем.

Главным образом потому, что вы можете получить значение промиса из другого окна браузера (например, iframe), у которого будет свой собственный промис, отличный от того, который находится в текущем окне или фрейме, и эта проверка не сможет идентифицировать экземпляр промиса.

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

Проглатывание исключений

Если в какой-либо момент создания промиса или наблюдения за его разрешением возникает ошибка исключения JavaScript, такая как TypeError или ReferenceError , это исключение будет перехвачено, и оно заставит рассматриваемое обещание быть отклоненным.

Например:

Но что произойдет, если обещание выполнено, но во время наблюдения произошла ошибка исключения JS (в затем (…) зарегистрированном обратном вызове)? Даже если это не будет потеряно, вы можете найти то, как с ними обращаются, немного удивительно. Пока вы не копнете немного глубже:

Похоже, что исключение из foo.bar() действительно было проглочено. Но это не так. Однако было что-то более глубокое, что пошло не так, к чему мы не прислушались. 9Сам вызов 0027 p.then(…) возвращает другое обещание, и именно это обещание будет отклонено с исключением TypeError .

Обработка неперехваченных исключений

Существуют и другие подходы, которые, по мнению многих, лучше .

Распространенное предложение состоит в том, что к промисам следует добавить done(…) , что по существу помечает цепочку промисов как «выполнено». done(…) не создает и не возвращает промис, поэтому обратные вызовы передаются на done(. .) , очевидно, не подключены для сообщения о проблемах связанному промису, которого не существует.

Обрабатывается так, как вы обычно ожидаете в условиях неперехваченной ошибки: любое исключение внутри обработчика отклонения done(..) будет выдано как глобальная неперехваченная ошибка (в основном в консоли разработчика):

Что происходит в ЭС8? Async/await

JavaScript ES8 представил async/await , который упрощает работу с промисами. Кратко пройдемся по возможностям 9Предложения 0027 async/await и как использовать их для написания асинхронного кода.

Как использовать асинхронность/ожидание?

Вы определяете асинхронную функцию, используя объявление функции async . Такие функции возвращают объект AsyncFunction. Объект AsyncFunction представляет собой асинхронную функцию, которая выполняет код, содержащийся в этой функции.

Когда вызывается асинхронная функция, она возвращает обещание . Когда асинхронная функция возвращает значение, это не Promise , будет автоматически создан Promise , и он будет разрешен с возвращаемым значением из функции. Когда асинхронная функция выдает исключение, обещание будет отклонено с выброшенным значением.

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

Вы можете думать о Promise в JavaScript как о эквиваленте Future в Java или C# Task.

Целью async/await является упрощение поведения при использовании промисов.

Давайте рассмотрим следующий пример:

Точно так же функции, генерирующие исключения, эквивалентны функциям, возвращающим обещания, которые были отклонены:

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

Вы также можете определить асинхронные функции, используя «выражение асинхронной функции». Выражение асинхронной функции очень похоже на оператор асинхронной функции и имеет почти такой же синтаксис. Основное различие между выражением асинхронной функции и оператором асинхронной функции заключается в имени функции, которое можно опустить в выражениях асинхронной функции для создания анонимных функций. Выражение асинхронной функции можно использовать как IIFE (выражение функции с немедленным вызовом), которое запускается, как только оно определено.

Это выглядит так:

Что еще более важно, async/await поддерживается во всех основных браузерах:

Если эта совместимость не то, что вам нужно, есть также несколько транспиляторов JS, таких как Babel и TypeScript.

В конце концов, важно не выбирать слепо «последний» подход к написанию асинхронного кода. Очень важно понимать внутренности асинхронного JavaScript, понимать, почему он так важен, и глубоко понимать внутренности выбранного вами метода. У каждого подхода есть свои плюсы и минусы, как и во всем остальном в программировании.

  1. Чистый код: Использование async/await позволяет писать намного меньше кода. Каждый раз, когда вы используете async/await, вы пропускаете несколько ненужных шагов: напишите .then, создайте анонимную функцию для обработки ответа, назовите ответ от этого обратного вызова, например.

Versus:

2. Обработка ошибок: Async/await позволяет обрабатывать как синхронные, так и асинхронные ошибки с помощью одной и той же конструкции кода — хорошо известных операторов try/catch. Давайте посмотрим, как это выглядит с промисами:

Versus:

3. Conditionals: Написание условного кода с async/await намного проще:

Versus:

4. В отличие от фреймов стека:8 async/await2 с стек, возвращенный из цепочки обещаний, не дает ни малейшего представления о том, где произошла ошибка. Посмотрите на следующее:

Versus:

5. Отладка: Если вы использовали обещания, вы знаете, что их отладка — это кошмар. Например, если вы установите точку останова внутри блока .then и используете ярлыки отладки, такие как «stop-over», отладчик не перейдет к следующему .then, потому что он только «шагает» через синхронный код.
С помощью async/await вы можете выполнять вызовы await точно так же, как если бы они были обычными синхронными функциями.

Почему подключение

асинхронного кода JavaScript важно для библиотек?

Позвольте мне привести пример с нашим собственным продуктом SessionStack. Библиотека SessionStack записывает все в вашем веб-приложении/веб-сайте: все изменения DOM, взаимодействия с пользователем, исключения JavaScript, трассировку стека, неудачные сетевые запросы и сообщения отладки.

И все это должно происходить в вашей производственной среде, не влияя на UX. Нам нужно сильно оптимизировать наш код и сделать его максимально асинхронным, чтобы мы могли увеличить количество событий, обрабатываемых циклом событий.

И не только библиотека! Когда вы воспроизводите пользовательский сеанс в SessionStack, мы должны визуализировать все, что происходило в браузере вашего пользователя в момент возникновения проблемы, и мы должны реконструировать все состояние, позволяя вам переходить назад и вперед по временной шкале сеанса. Чтобы сделать это возможным, мы активно используем асинхронные возможности, предоставляемые JavaScript.

Заинтересованы в SessionStack? Начните работу за минут с нашим бесплатным планом .

Довольны SessionStack? Оставьте отзыв и получите Подарочную карту!

Ресурсы:

  • https://github.com/getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch3.md
  • https://github.com /getify/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch4.
Оставить комментарий

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *