Audytor.ru

Теплоснабжение "Аудитор"
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Разбираемся с Async/Await в JavaScript на примерах

Разбираемся с Async/Await в JavaScript на примерах

Обложка: Разбираемся с Async/Await в JavaScript на примерах

Callback — это не что-то замысловатое или особенное, а просто функция, вызов которой отложен на неопределённое время. Благодаря асинхронному характеру JavaScript, обратные вызовы нужны были везде, где результат не может быть получен сразу.

Ниже приведён пример асинхронного чтения файла на Node.js:

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

  • Выполняется запрос в БД на некого пользователя Arfat . Нужно считать его поле profile_img_url и загрузить соответствующее изображение с сервера someServer.ru .
  • После загрузки изображения необходимо его конвертировать, допустим из PNG в JPEG.
  • В случае успешной конвертации нужно отправить письмо на почту пользователя.
  • Это событие нужно занести в файл transformations.log и указать дату.

Обратите внимание на вложенность обратных вызовов и пирамиду из >) в конце. Подобные случаи принято называть Callback Hell или Pyramid of Doom. Вот основные недостатки:

  • Такой код сложно читать.
  • В таком коде сложно обрабатывать ошибки и одновременно сохранять его «качество».

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

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

  • Всё ещё нужно работать с кучей .then .
  • Вместо обычного try/catch нужно использовать .catch для обработки всех ошибок.
  • Работа с несколькими промисами в цикле не всегда интуитивно понятна и местами сложна.

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

Предположим, что у вас есть цикл for , который выводит последовательность чисел от 0 до 10 со случайным интервалом (от 0 до n секунд). Используя промисы нужно изменить цикл так, чтобы числа выводились в строгой последовательности от 0 до 10. К примеру, если вывод нуля занимает 6 секунд, а единицы 2 секунды, то единица должна дождаться вывода нуля и только потом начать свой отсчёт (чтобы соблюдать последовательность).

PricewaterhouseCoopers , Удалённо , По итогам собеседования

Стоит ли говорить, что в решении этой задачи нельзя использовать конструкцию async/await либо .sort функцию? Решение будет в конце.

Когда я начинал изучать JavaScript, концепция this показалась мне крайне запутанной. Daniel James написал прекрасную статью о том, как проще всего усвоить концепцию this в JS.

Введение

Быстрый рост популярности JS отчасти можно объяснить низким порогом вхождения. Такие особенности языка как функции и this обычно работают, как ожидается. Для того, чтобы стать профессионалом в JS, Вам не нужно знать множества мелких деталей и подробностей (я бы с этим поспорил — прим. пер.). Но однажды каждый разработчик сталкивается с ошибкой, причиной которой является значение this.

Читайте так же:
Kyocera fs 1035mfp сброс счетчика картриджа

После этого у Вас возникает желание разобраться с тем, как работает this в JS. Является ли this концепцией объектно-орентированного программирования (ООП)? Является ли JS объектно-ориентированным языком программирования (ООЯП)? Если вы «погуглите» это, то получите в ответ упоминания каких-то прототипов. Что еще за прототипы? Для чего использовалось ключевое слово «new» до появления классов в JS?

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

ООП в JS

Парадигма прототипного программирования (наследования) в JS является одной из характерных черт ООП. Еще до появления классов JS был ООЯП. JS — простой язык, использующий лишь несколько вещей из ООП. Наиболее важными из них являются функции, замыкания, this, прототипы, объектные литералы и ключевое слово «new».

Инкапсуляция и повторное использование (Reusability) с помощью замыканий

Давайте создадим класс Counter (счетчик). У этого класса должны быть методы сброса и увеличения счетчика. Мы можем написать что-то вроде этого:

В данном случае мы ограничились использованием функций и объектных литералов без this или new. Да, мы уже получили кое-что из ООП. У нас имеется возможность создавать новые экземпляры Counter. У каждого экземпляра Counter есть своя внутренняя переменная count. Мы реализовали инкапсуляцию и повторное использование чисто функциональным способом.

Проблема производительности

Предположим, что мы пишем программу, использующую большое количество счетчиков. У каждого счетчика будут собственные методы reset и next (Counter().reset != Counter().reset). Создание таких замыканий для каждого метода каждого экземпляра потребует колоссального объема памяти! Такая архитектура «нежизнеспособна». Поэтому нам необходимо найти способ хранить в каждом экземпляре Counter только ссылки на используемые им методы (по сути, это то, что делают все ООЯП, такие как Java).

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

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

This спешит на помощь

Перепишем наш пример с использованием this:

Обратите внимание, что мы по-прежнему создаем простые функции reset и next (Cunter.new().reset == Counter.new().reset). В предыдущем примере для того, чтобы программа работала, мы были вынуждены предоставлять совместно реализуемым методам дескриптор экземпляра. Теперь мы просто вызываем myCounter.next() и ссылаемся на экземпляр с помощью this. Но как это работает? Reset и next объявлены в объекте Counter. Откуда JS знает, на что ссылается this при вызове функции?

Вызов функций в JS

Вы прекрасно знаете, что у функций в JS есть метод call (также существует метод apply; разница между этими методами несущественна. Разница состоит в том, как мы передаем параметры: в apply в виде массива, в call через запятую — прим. пер.). Используя call, Вы решаете какое значение будет иметь this при вызове функции:

Читайте так же:
Топливораздаточные краны с счетчиком

В действительности это то, что делает точечная нотация за сценой, когда мы вызываем функцию. lhs.fn() идентично fn.call(lhs).

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

Начинаются проблемы

Скажем, Вы хотите создать счетчик и увеличивать его значение каждую секунду. Вот как это можно сделать:

Вы видите здесь ошибку? Когда setInterval запускается, значение this равняется undefined, поэтому ничего не происходит. Эту проблему можно решить так:

Немного о bind

Существует другой способ решения данной проблемы:

Используя «фабричную» функцию bindThis, мы можем быть уверены, что Counter.next всегда вызывает myCounter в качестве this, независимо от того, как вызывается новая функция. На самом деле мы не изменяем функцию Counter.next. JS имеет встроенный метод bind. Поэтому мы можем переписать пример выше так: setInterval(myCounter.next.bind(myCounter), 1000).

Работаем с прототипами

На данный момент у нас есть неплохой класс Counter, но он по-прежнему немного «кривой». Речь идет о следующий строчках:

Нам нужен лучший способ делиться методами класса с его экземплярами. С этой задачей отлично справляются прототипы. Если Вы обратитесь к свойству функции или объекта, которой не существует, JS будет искать данное свойство в прототипе этой функции или объекта (затем в прототипе прототипа и так до Object.prototype, находящегося на вершине цепочки прототипов — прим. пер.). Вы можете определить прототип объекта с помощью Object.setPrototypeOf. Давайте перепишем наш класс Counter, используя прототипы:

Ключевое слово «new»

Использование setPrototypeOf очень похоже на то, как работает оператор «new». Отличие заключается в том, что new будет использовать прототип конструктора переданной функции. Поэтому, вместо создания объекта для наших методов, мы передаем их в прототип конструктора функции:

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

Ключевое слово «class»

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

Ключевое слово «class» ничего особенно под «катом» не делает. Вы можете думать о нем, как о синтаксическом сахаре, обертке «прототипного» подхода. Если Вы запустите транспилятор, ориентированный на ES3, Вы получите что-то вроде этого:

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

Стрелочные функции

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

Читайте так же:
Как считать цифровой счетчик

This в стрелочных функциях

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

Пара for…of и for…in

Эта конструкция очень похожа на предыдущую, но в тоже время имеет свои особенности.

Цикл for…in обрабатывает несимвольные, перечисляемые свойства объекта (ключевое слово здесь — «объект», потому что почти всё в JavaScript является объектом). Этот цикл особенно полезен, когда вы используете пользовательский объект в качестве хэш-карты или словаря (очень распространённая практика).

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

Вместо того, чтобы перебирать каждую букву строки, цикл перебирал каждое свойство, и, как видите, эта структура (для строкового типа), очень похожа на массив. И в этом есть смысл. Если задать «Hello World!» , цикл не сработает и вернет букву «e».

Если вы хотите перебрать каждый символ, то нужно использовать вариант: for…of

Вот теперь в этом есть смысл. Та же задача, но с for…of вы получаете доступ к значениям итерируемого объекта (итерируемыми могут быть строки, массивы, карты, наборы и структуры подобные массивам, такие как arguments и NodeList ), конечно, если вы определяете их как итерируемые.

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

А что здесь с асинхронным кодом? Совершенно то же самое.

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

Как определить где необходимо замыкание?

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

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

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

Читайте так же:
Если не показывает счетчик яндекса

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

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

Подробный разбор примеров работы с замыканиями

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

▍Пример №1

Взгляните на данный фрагмент кода:

Когда выполняется функция person() , JS-движок создаёт новый контекст выполнения и новое лексическое окружение для этой функции. Завершая работу, функция возвращает функцию displayName() , в переменную peter записывается ссылка на эту функцию.

Её лексическое окружение будет выглядеть так:

Когда функция person() завершает работу, её контекст выполнения извлекается из стека. Но её лексическое окружение остаётся в памяти, так как ссылка на него есть в лексическом окружении её внутренней функции displayName() . В результате переменные, объявленные в этом лексическом окружении, остаются доступными.

Это лексическое окружение будет выглядеть так: Когда вызывается функция peter() (соответствующая переменная хранит ссылку на функцию displayName() ), JS-движок создаёт для этой функции новый контекст выполнения и новое лексическое окружение.

displayNameLexicalEnvironment = < environmentRecord: < >outer: <personLexicalEnvironment>
>

В функции displayName() нет переменных, поэтому её запись окружения будет пустой. В процессе выполнения этой функции JS-движок попытается найти переменную name в лексическом окружении функции.

Там движок находит нужную переменную и выводит её значение в консоль. Так как в лексическом окружении функции displayName() искомое найти не удаётся, поиск продолжится во внешнем лексическом окружении, то есть, в лексическом окружении функции person() , которое всё ещё находится в памяти.

▍Пример №2

function getCounter() < let counter = 0; return function() < return counter++; >
>
let count = getCounter();
console.log(count()); // 0
console.log(count()); // 1
console.log(count()); // 2

Лексическое окружение функции getCounter() будет выглядеть так:

Эта функция возвращает анонимную функцию, которая назначается переменной count .

Когда выполняется функция count() , её лексическое окружение выглядит так:

countLexicalEnvironment = < environmentRecord: < >outer: <getCountLexicalEnvironment>
>

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

Движок находит переменную, выводит её в консоль и инкрементирует переменную counter , хранящуюся в лексическом окружении функции getCounter() .

В результате лексическое окружение функции getCounter() после первого вызова функции count() будет выглядеть так:

При каждом следующем вызове функции count() JavaScript-движок создаёт новое лексическое окружение для этой функции и инкрементирует переменную counter , что приводит к изменениям в лексическом окружении функции getCounter() .

Что надо помнить

На функцию из рассматриваемых методов создается внутренняя ссылка и прописывается в планировщик. Сохранившись там, она не попадет в Garbage Collector даже в том случае, когда на нее ничто не ссылается. Функцию же setInterval сотрет из памяти только метод clearInterval .

Читайте так же:
Что такое индуктивный счетчик

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

4. Зачем мы это замудрили?

Все эти манипуляции были созданы для того, чтобы иметь возможность создавать любое количество НЕЗАВИСИМЫХ друг от друга счётчиков, под любые задачи.

У нас уже есть счётчик s1 . Последним значением он вернул 4. Давайте теперь создадим новый счётчик и назовём его ddd.

Запустим счётчик ddd два раза.

А теперь запустим s1 .

Смотрим историю вызовов:

Создали и запустили новый счётчик ddd

Создали и запустили новый счётчик ddd

Второй вызов ddd вернул нам значение 2. Следующий за ним вызов s1 вернул нам значение 5. Это значит, что в оперативной памяти появилась новая замкнутая переменная x (икс), которая живёт во втором клоне оригинальной функции counter .(в ddd)

Стрелочные функции или лямбда выражения в javascript

В javascript возможно использование так называемых лямбда выражений: функции, которые сокращают запись выражения. В записи такой функции используется стрелочка => , отсюда и название «стрелочные функции». Стрелочку можно трактовать, как «такое, что»:

Пример стрелочной функции с одним аргументом:

let f = x => x + 1; alert(f(5))

Расшифровываем так:
функция f равна значению x, такое, что x = x + 1.
То есть в результате в окно выведется 6.

Пример стрелочной функции с двумя аргументами:

let g = (x, y) => x + y; alert(g(5,3))

  • Назовем функцию factorial.
  • Определим рекурсивные правила:
  • Теперь данные правила опишем в самой рекурсивной функции в скрипте. Для написания функции будем использовать тернарный оператор:

let factorial = n => (n > 0) ? n * factorial(n-1) : 1;

  • Назовем функцию fib.
  • Определим рекурсивные правила:
  • Теперь данные правила опишем в самой рекурсивной функции в скрипте. Для написания функции будем использовать тернарный оператор:

let fib = n => (n > 2) ? fib(n-1) + fib(n-2) : 1;

  1. Какие функции называются стрелочными функциями?
  2. Есть ли разница между стрелочными функциями и лямбда выражениями в javascript?
  3. Каков синтаксис стрелочных функций?
Рубрики:

В вашем задание ответ неправильный. Ответ будет 6 и 6. Попробуйте сами запустить этот код function Plrectangle(width, height) <
S = width * height;
return S
>
S=2;
z = Plrectangle(2, 3);
alert(z);
alert (S);
Пример: Значение z равно 6, а значение S осталось равным 2, то есть значению глобальной переменной, определенной во внешней программе

admin

Да нет, всё верно

Dmitriy

Всё же не верно. S — глобальная переменная, и в теле функции S изменяется. Результат 6 и 6. Чтобы было верно, надо в в функции поставить var S=…

голоса
Рейтинг статьи
Ссылка на основную публикацию
Adblock
detector