Javascript цикл: Повторять цикл, пока ввод неверен

Содержание

Я ненавижу циклы for в JavaScript. Позвольте мне рассказать вам, почему. | Джоэл Томс

Я ненавижу цикл for и считаю его ужасной конструкцией. Позвольте мне объяснить вам, почему…

Несмотря на то, что это явно предвзятый хит, на самом деле существует масса действительно полезной информации о цикле for в JavaScript.

примечание: эта проблема касается только циклов for в JavaScript.

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

Цикл for состоит из 4 основных частей. Ну, технически 5, но вы никогда не должны использовать ярлык, поэтому я просто помечу это как 💀. Четвертая часть также содержит пару дополнительных необязательных подразделов. Метка

(💀) — используется для кастомного взлома. Использование ярлыков считается антишаблоном, так что просто не делайте этого.
(1) инициализация — выполняется один раз перед началом цикла.
(2) условно — выполняется перед каждой итерацией. Здесь должен произойти любой break .
(3) итерация — выполняется после каждой итерации. Обычно используется для увеличения счетчика. 9Операторы 0015 (4) — здесь живет ваш код.
(4.1) continue (необязательно) — прекратит выполнение инструкций и перейдет к следующей итерации.
(4.2) break (необязательно) — прекратит выполнение инструкций и выйдет из цикла for.

Совет : используйте Bit для организации, совместного использования и обнаружения компонентов Javascript — и быстрее создавайте новые приложения. Это сэкономит время вашей команды и облегчит жизнь.

React Spinners with Bit: выберите, изучите и установите

Component Discovery and Collaboration · Bit

Bit — это место, где разработчики обмениваются компонентами и сотрудничают, чтобы вместе создавать потрясающее программное обеспечение.

Откройте для себя общие компоненты…

bit.dev

В JavaScript не существует только одного цикла for. В JavaScript на самом деле есть три разных цикла for .

Вы можете использовать цикл for/in для перебора ключей массива.

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

Цикл for/in в JavaScript также не работает так же, как цикл foreach/in в C#. Так что это будет сбивать с толку.

Вы можете использовать "для/из" для перебора значений массива или итерируемого.

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

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

При инициализации с помощью var значения будут доступны вне цикла.

При инициализации с помощью let значения будут ограничены циклом.

Теперь я разберу операции цикла for. Таким образом, мы лучше поймем, как это работает под капотом.

давайте начнем с простого примера цикла for:

Что код выше делает в псевдокоде:

Обратите внимание на последние 2 оператора (строки 12 и 13), а именно i++ . По этой причине в разделе var и let последний console.log выводит end: 3 .

Оператор инициализации выполняется до начала цикла for, а итерация выполняется после выполнения операторов.

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

Но для (; i < array.length;) выглядит глупо. Я мог бы просто сократить это до while (i < array.length) .

Тогда объясните мне, почему мой первый переход — это цикл for, а не время?

Примечание: пусть работает по-разному, поскольку значение ограничено циклом.

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

Итак, теперь я могу написать безумный цикл for, подобный этому:

Нажмите , запустите на этом чудовище, чтобы посмотреть, что произойдет.

Домашнее задание: Рефакторинг приведенного выше кода для выполнения цикла for с использованием функций для init , Conditional , post и операторов .

Функциональные программисты не любят цикл for, потому что он по своей природе нечист. Всякий раз, когда я замечаю цикл for, мой мозг сигнализирует ⚠️ Danger , потому что я знаю, что скоро увижу мутации и побочные эффекты.

Невозможно сложить значения с помощью цикла for неизменяемым образом.

Цикл for не может быть чистым.

Почему в этом примере суммирует вне цикла?

Цикл for содержит раздел инициализации, так почему бы не включить sum в раздел инициализации?

Почему мы используем break вместо условного . В конце концов, это и есть цель условного !

Это похоже на написание вложенного оператора if, когда вы должны объединить их в один:

Оба эти варианта, возможно, лучше, потому что break происходит в одном месте, но мы увидим код, написанный «сумасшедшим стилем». «В 99,9% случаев.

И почему мы пишем i++ вместо ++i , когда ++i более интуитивно понятно?

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

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

Тем не менее, многие программисты любят его и даже предпочитают его альтернативам, таким как map , filter и reduce .

Хотя мне кажется, что это гораздо легче читать…

… по сравнению с циклом for.

Это настолько проще, что вы, вероятно, даже не заметили ошибку в коде цикла for!

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

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

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

Решение №1. Заканчивайте!

Решение «наименьшего объема работы» — завернуть все!

Цикл for сам по себе может быть не чистым, но его обертывание функцией может привести к чистой функции.

Я бы даже предложил все циклы for обернуть в функцию .

Использовать карту/фильтр/уменьшение

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

Используйте библиотеку

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

Я постоянно слышу это: «цикл for быстрее, чем forEach, так почему бы мне не всегда использовать более быстрый вариант?»

Отличный вопрос о производительности! К счастью, нет реальной разницы в производительности между циклом for и map/filter/reduce .

У вас может возникнуть соблазн перейти на сайт jsperf.com и начать вставлять метрики. Но метрики из jsperf не только бессмысленны (в 99,99% приложений), но вы, вероятно, измеряете неправильные метрики.

Чтобы доказать мой аргумент,

Если бы вы полагались на эти показатели, вы бы поверили, что вам следует использовать +"10" вместо parseInt("10", 10) из-за 64% улучшение скорости.

Это не более чем отвлекающий маневр.

Хотя этот изолированный участок вашего кода будет на 64% быстрее, этот раздел уже настолько быстрый, что 64% ​​бессмысленны. Это «улучшение», которое вы сделаете, приведет к тому, что ваше приложение будет работать на 0,00 мс быстрее . Правильно, ваше приложение будет работать с той же скоростью без каких-либо улучшений.

Вам гораздо лучше обратить внимание на нотацию Big-O, цикломатическую сложность, задержку API или кэширование, чтобы добиться реальных улучшений скорости.

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

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

В JavaScript не один, а целых три цикла for. Самый распространенный цикл for состоит из 7 частей, 2 из которых являются необязательными, а 1 никогда не следует использовать. Цикл for/in работает иначе, чем в других языках, таких как C#. Цикл for никогда не может быть чистым, он может быть заполнен либо мутациями, либо побочными эффектами, либо и тем, и другим. Наконец, читабельность цикла for оставляет желать лучшего, и вам нужно визуально проанализировать весь цикл for, прежде чем вы сможете понять его назначение.

Честно говоря, я все еще пишу циклы for. Но только когда все остальные варианты не работают . И случаи, когда я пишу цикл for, становятся все более редкими.

Я представляю себе лучший мир, в котором не существует цикла for.

Я пишу об удивительных вещах для fp. Следите за мной, чтобы быть в курсе. Вы также можете найти меня в Твиттере @joelnet. Я дружелюбный, задавайте мне вопросы в комментариях!

Ура!

Понимание шаблонов проектирования в JavaScript

Узнайте о различных шаблонах проектирования в JavaScript

blog.bitsrc.io

11 служебных библиотек Javascript, которые вы должны знать в 2019 году

11 полезных служебных библиотек Javascript для ускорения разработки.

blog.bitsrc.io

Понимание контекста выполнения и стека выполнения в Javascript

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

blog.bitsrc.io

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 первый 
третий
второй

Что такое цикл событий?

Мы начнем с довольно странного утверждения — несмотря на то, что асинхронный код 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(...) завершается, наша сумма будущего значения готов и мы можем его распечатать. Мы скрываем логику ожидания будущих значений x и y внутри sum(...) .

Примечание : Внутри sum(…) вызов Promise. all([ … ]) создает обещание (которое ожидает разрешения promiseX и promiseY ). Связанный вызов .then(...) создает другое обещание, которое возвращает
values[0] + values[1] 9Строка 0018 сразу разрешается (с результатом сложения). Таким образом, вызов 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Сам вызов 0017 p.then(…) возвращает другое обещание, и именно это обещание будет отклонено с исключением TypeError .

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

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

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

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

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

JavaScript ES8 представил async/await , который упрощает работу с промисами. Кратко пройдемся по возможностям 9Предложения 0017 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. стек, возвращенный из цепочки обещаний, не дает ни малейшего представления о том, где произошла ошибка. Посмотрите на следующее:

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.md
  • http://nikgrozev.com/2017/10/01/async-await/

Заинтересовано подробнее о JavaScript? Ознакомьтесь со всеми публикациями «Как работает JavaScript» здесь.

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

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