Vvmebel.com

Новости с мира ПК
1 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Асинхронное программирование js

Async/await

Существует специальный синтаксис для работы с промисами, который называется «async/await». Он удивительно прост для понимания и использования.

Асинхронные функции

Начнём с ключевого слова async . Оно ставится перед функцией, вот так:

У слова async один простой смысл: эта функция всегда возвращает промис. Значения других типов оборачиваются в завершившийся успешно промис автоматически.

Например, эта функция возвратит выполненный промис с результатом 1 :

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

Так что ключевое слово async перед функцией гарантирует, что эта функция в любом случае вернёт промис. Согласитесь, достаточно просто? Но это ещё не всё. Есть другое ключевое слово – await , которое можно использовать только внутри async -функций.

Await

Ключевое слово await заставит интерпретатор JavaScript ждать до тех пор, пока промис справа от await не выполнится. После чего оно вернёт его результат, и выполнение кода продолжится.

В этом примере промис успешно выполнится через 1 секунду:

В данном примере выполнение функции остановится на строке (*) до тех пор, пока промис не выполнится. Это произойдёт через секунду после запуска функции. После чего в переменную result будет записан результат выполнения промиса, и браузер отобразит alert-окно «готово!».

Обратите внимание, хотя await и заставляет JavaScript дожидаться выполнения промиса, это не отнимает ресурсов процессора. Пока промис не выполнится, JS-движок может заниматься другими задачами: выполнять прочие скрипты, обрабатывать события и т.п.

По сути, это просто «синтаксический сахар» для получения результата промиса, более наглядный, чем promise.then .

Если мы попробуем использовать await внутри функции, объявленной без async , получим синтаксическую ошибку:

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

Давайте перепишем пример showAvatar() из раздела Цепочка промисов с помощью async/await :

  1. Нам нужно заменить вызовы .then на await .
  2. И добавить ключевое слово async перед объявлением функции.

Получилось очень просто и читаемо, правда? Гораздо лучше, чем раньше.

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

Можно обернуть этот код в анонимную async –функцию, тогда всё заработает:

Как и promise.then , await позволяет работать с промис–совместимыми объектами. Идея в том, что если у объекта можно вызвать метод then , этого достаточно, чтобы использовать его с await .

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

Когда await получает объект с .then , не являющийся промисом, JavaScript автоматически запускает этот метод, передавая ему аргументы – встроенные функции resolve и reject . Затем await приостановит дальнейшее выполнение кода, пока любая из этих функций не будет вызвана (в примере это строка (*) ). После чего выполнение кода продолжится с результатом resolve или reject соответственно.

Для объявления асинхронного метода достаточно написать async перед именем:

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

Обработка ошибок

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

Делает тоже самое, что и такой:

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

Такие ошибки можно ловить, используя try..catch , как с обычным throw :

В случае ошибки выполнение try прерывается и управление прыгает в начало блока catch . Блоком try можно обернуть несколько строк:

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

Если забыть добавить .catch , то будет сгенерирована ошибка «Uncaught promise error» и информация об этом будет выведена в консоль. Такие ошибки можно поймать глобальным обработчиком, о чём подробно написано в разделе Промисы: обработка ошибок.

При работе с async/await , .then используется нечасто, так как await автоматически ожидает завершения выполнения промиса. В этом случае обычно (но не всегда) гораздо удобнее перехватывать ошибки, используя try..catch , нежели чем .catch .

Но на верхнем уровне вложенности (вне async –функций) await использовать нельзя, поэтому .then/catch для обработки финального результата или ошибок – обычная практика.

Так сделано в строке (*) в примере выше.

Когда необходимо подождать несколько промисов одновременно, можно обернуть их в Promise.all , и затем await :

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

Итого

Ключевое слово async перед объявлением функции:

  1. Обязывает её всегда возвращать промис.
  2. Позволяет использовать await в теле этой функции.

Ключевое слово await перед промисом заставит JavaScript дождаться его выполнения, после чего:

  1. Если промис завершается с ошибкой, будет сгенерировано исключение, как если бы на этом месте находилось throw .
  2. Иначе вернётся результат промиса.

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

Асинхронный JavaScript ― Цикл обработки событий

В этой статье вы узнаете, как работает асинхронность в JavaScript

JavaScript — однопоточный язык программирования. Движок JS единовременно может обрабатывать только одно выражение — в одном потоке.

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

Представьте себе запрос данных из API. В зависимости от ситуации, серверу потребуется какое-то время на обработку запроса, а пока основной поток занят — страница будет неотзывчива.

В таких случаях нужна асинхронность. Используя асинхронный JavaScript (например callback, promise, и async/await), вы можете выполнять длительные сетевые запросы, не блокируя основной поток.

Необязательно знать, что происходит «под капотом» JavaScript, но полезно понимать, как это работает?

Как работает синхронный JavaScript?

Перед тем как перейти к асинхронному JavaScript, сначала разберёмся — как выполняется синхронный код внутри движка JS. Например, этот:

Чтобы понять, как выполняется этот код внутри JavaScript, нам нужно знать, что такое контекст выполнения и стек вызовов (или, стек выполнения).

Контекст выполнения

Это абстрактная концепция окружения, где код анализируется и выполняется. Всякий раз, когда в JavaScript выполняется код, это происходит внутри контекста выполнения.

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

Стек вызовов

Здесь название говорит само за себя. Это стек со структурой LIFO (Last in, First out), в котором хранятся все контексты выполнения, созданные в течение выполнения кода.

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

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

Итак, что же здесь происходит?

При исполнении этого кода, был создан глобальный контекст выполнения ( main() ) и помещён в стек, сверху. Когда в коде встречается вызов first() , он также помещается в стек, сверху.

Далее, в стек попадает console.log(‘Hi there!’) , также сверху, а после завершения вылетает из стека. Далее мы вызываем функцию second() , с ней происходит то же самое.

Далее, console.log(‘Hello there!’) попадает в стек и после завершения вылетает из него. Функция second() завершается и то же вылетает из стека.

console.log(‘The End’) попадает в стек и вылетает после завершения. После этого завершается функция first() и вылетает из стека.

На этом выполнение программы завершено и глобальный контекст выполнения ( main() ) вылетает из стека.

Как работает асинхронный JavaScript?

Теперь, когда у вас есть представление о стеке вызовов и синхронном JS, можно переходить к асинхронному JavaScript.

Что такое Блокировка?

Допустим, мы обрабатываем изображение и сетевой запрос, синхронным способом. Например:

На обработку изображения и сетевого запроса потребуется время. Продолжительность выполнения функции processImage() зависит от размера изображения.

После своего завершения функция processImage() вылетает из стека, после чего мы видим вызов функции networkRequest() , которая отправляется в стек. И всё это требует время на выполнение.

После завершения networkRequest() , вызывается функция greeting() , она выполняется незамедлительно, так как содержит только отчёт console.log, который выполняется довольно быстро.

Как видите, мы должны ожидать завершения функции (в нашем случае processImage() или networkRequest() ). Эти функции блокируют стек выполнения или основной поток. До их завершения мы не сможем выполнить другие операции.

Какое здесь может быть решение?

Наиболее простое решение — использовать асинхронные callback’и, чтобы избавить наш код от блокировок. Например:

Я использовал метод setTimeout , чтобы симулировать сетевой запрос. Имейте в виду, что этот метод является частью web API (в браузере) и C/C++ API (в node.js), в движке JavaScript этого метода нет.

Читать еще:  Классы в программировании пример

Чтобы понять, как выполняется этот код, нужно разобраться с ещё двумя концепциями: event loop (цикл обработки событий) и callback queue (очередь сообщений).

Event loop, web API и callback queue ― не являются частью движка JavaScript. Это всё относится к среде выполнения браузера или среде выполнения Nodejs (если речь идёт о Nodejs). В Nodejs, web API заменяется C/C++ API.

Давайте посмотрим, как выполняется код асинхронным способом.

Когда этот код загружается в браузер, в стек попадает console.log(‘Hello World’) , и удаляется из стека после своего завершения. Далее в стек попадает networkRequest() .

Далее в стеке оказывается функция setTimeout() . У этой функции есть два аргумента: 1) callback и 2) время в микросекундах (мкс).

Метод setTimeout() запускает таймер на 2s в окружении web API. На этом этапе выполнение setTimeout() завершено и он удаляется из стека. Далее в стек попадает console.log(‘The End’) , и после своего завершения также удаляется из стека.

Тем временем таймер истёк, и теперь callback попадает в message queue(очередь сообщений). Но callback выполняется не сразу, и именно здесь начинается цикл обработки событий.

Цикл обработки событий

Задача цикла обработки событий ― следить за состоянием стека вызовов и определять пуст он или нет. Если стек пуст, то проверяется message queue, нет ли там вызовов, ожидающих выполнения.

В нашем случае, message queue содержит один callback, а стек вызовов в этот момент пуст. Таким образом цикл обработки событий отправляет этот callback в стек.

После этого, в стек отправляется console.log(‘Async Code’) , и после выполнения удаляется из него. На этом этапе, выполнение callback завершено, и он удалён из стека. Программа наконец-то завершена.

Message queue также содержит callback’и от DOM-событий, такие как click events и keyboard events. Например:

В случае с DOM-событиями, «прослушиватель событий» находится в веб API окружении, ожидая определённого события (в этом случае click event). Когда событие происходит — callback функция помещается в message queue и ожидает выполнения.

Цикл обработки событий проверяет, пуст ли стек вызовов. Если стек пуст, то в него отправляется event callback для дальнейшего исполнения.

Отсрочка Выполнения Функции

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

Разберём этот код: первой вызывается функция foo() ; внутри foo вызывается console.log(‘foo’) ; далее вызывается setTimeout() с bar() в качестве callback и таймером 0 seconds .

Если мы не используем setTimeout , то функция bar() будет выполнена незамедлительно, но используя setTimeout с таймером 0 секунд ― мы откладываем выполнение bar , до опустошения стека.

После того как таймер истёк, callback функция bar() помещается в message queue, где она ожидает исполнения. Но она начнёт выполняться только тогда, когда стек будет полностью пуст, то есть после завершения функций baz и foo .

Очередь заданий в ES6

Мы узнали, как выполняются асинхронные callback’и и DOM events, которые используют message queue для хранения всех callback’ов, ожидающих исполнения.

В ES6 представили концепцию job queue (очередь заданий), которая используется промисами. Разница между message queue и job queue в том, что последний имеет более высокий приоритет. Это означает, что промисы внутри job queue исполняются перед callback’ами внутри message queue.

Мы видим, что промисы выполняются до setTimeout , потому что их ответ хранится в job queue, у которого более высокий приоритет чем у message queue.

Заключение

Итак, мы узнали, как работает асинхронный JavaScript и такие концепции как call stack, event loop, message queue и job queue. Вместе они создают среду выполнения JavaScript. Знать все эти понятия не обязательно, чтобы быть классным JavaScript-разработчиком, но точно будет полезно.

Руководство по асинхронным функциям ES7

Если вы следите за миром JavaScript, вы, вероятно, уже слышали об обещаниях (promises). Есть отличная статья, посвященная обещаниям, я не буду объяснять принципы работы с ними. Эта статья предполагает, что вы уже хорошо знакомы с обещаниями.

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

В ECMAScript 7 это станет больше, чем просто мечтой: это станет реальностью. Я покажу вам эту реальность — называемую асинхронными функциями — прямо сейчас. Почему мы говорим об этом сейчас? Ведь даже ES6 не был полностью завершен (спецификация была одобрена в июне 2015 года, после выхода статьи — прим. переводчика), так что кто знает, сколько времени пройдет, прежде чем мы увидим ES7. Правда, вы можете использовать эту технологию уже сейчас, и в конце этой статьи я покажу вам как.

Текущее положение дел

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

Примеры

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

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

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

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

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

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

Разве вы не согласны, что это нужно исправить? Давайте рассмотрим решение.

Спасение в асинхронных фукциях

Даже с обещаниями асинхронное программирование не всегда просто и понятно от А до Я. Синхронный код гораздо проще для написания и читается тоже более естественно. Асинхронные функции — это средство писать асинхронный код так же, как синхронный (за сценой используются генераторы ES6).

Как мы будем их использовать?

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

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

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

Давайте преобразуем функции getValues и asyncOperation:

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

Когда вы вызываете foo, она ждет выполнения функции promisingOperation и выводит либо «Success!», если обещание успешно, либо «Failure!», если неуспешно.

Остается один вопрос: как обработать неуспешное обещание? Ответ просто: нам нужно обернуть вызов в блок try. catch. Если одна из асинхронных операций завершится неуспешно, сработает блок catch.

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

Примеры

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

Читать еще:  Языки программирования для приложений

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

Вы, наверняка, заметили звездочку после последнего выражения await. await* автоматически обернет результат в Promise.all. Однако инструмент, о котором мы поговорим позже, не поддердивает await*, поэтому можно использовать await Promise.all(newValues), как мы и сделаем в следующем примере.

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

Мне это нравится. Это чистый код. Если убрать все await и async, удалить обертку Promise.all и сделать функции getValues и asyncOperation синхронными, этот код будет работать в точности так же, как если бы он был синхронным. Именно этого, по существу, мы и стремимся достичь.

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

Снова мы делаем внутреннюю функцию асинхронной. Есть одна интересная особенность, которая проявляется в этом коде. Я передал пустой массив в качестве знаения «memo» в функцию reduce, но потом я использовал await c этим значением. Значение, которое используется с await, не обязательно должно быть массивом. Оно может принимать любые значения, но если это не объект Promise, код просто выполняется синхронно. И конечно, после первого прохода, мы уже будем работать с обещанием.

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

Использование асинхронных функций сегодня

Теперь, когда вы получили представление о красоте и удивительности асинхронных функций, вы можете плакать, как делал это я, когда впервые о них узнал. И я плакал не от радости, нет. Я плакал потому, что ES7 появится не раньше, чем я умру! По крайней мере так я себя чувствовал. И тогда я узнал о Traceur.

Traceur написан и поддерживается Google. Это транслятор, конвертирующий код на ES6 в код на ES5. Но он также поддерживает асинхронные функции. Это экспериментальная особенность, поэтому вам нужно явно сказать трянслятору, что вы хотите использовать асинхронные функции. А также стоит тщательно проверить результат трансляции.

Использование транслятора, такого как Traceur, означает, что вы будете отдавать клиенту немного раздутый и уродливый код. Но работать вы будете с чистым ES6/7 кодом, вместо запутаного.

Конечно, размер кода будет больше (в большинстве случаев), чем при ручном написании на ES5, поэтому нужно искать равновесие между поддерживаемым кодом и производительностью.

Использование Traceur

Traceur — это утилита командной строки, которая может быть установлена через NPM:

В основном, Traceur очень прост в использовании, но некоторые опции требуют экспериментов. Нас будет интересовать опция —experimental.

Эту опцию следует использовать, чтобы включить экспериментальные особенности, такие как асинхронные функции. Допустим, у нас есть JavaScript файл (в данном случае main.js) с ES6 кодом и асинхронными функциями, мы можем транслировать данный код:

Вы также можете просто запустить команду выше, опустив —out compiled.js. Вы не увидите ничего, если код не содержит вызовов console.log (или других методов вывода в консоль), но, по крайней мере, вы можете проверить код на наличие ошибок. Чтобы запустить код в браузере, необходимо предпринять несколько шагов:

  • Скачать traceur-runtime.js. Проще всего сделать это через npm: npm install traceur-runtime. Скрипт будет доступен как index.js в папке модуля.
  • Подключить скрипт Traceur Runtime в вашем HTML-файле.
  • Подключить скрипт compiled.js.

После этого код можно запустить!

Автоматизация компиляции

Запускать компиляцию Traceur можно не только вручную из командной строки, но и с помощью таких инструментов, как Grunt или Gulp. Каждый из их этих сборщиков можно настроить таким образом, чтобы он следил за изменениями файлов в проекте и запускал перекомпиляцию после кадого изменения. Подробнее о каждом сборщике можно прочитать в соответствующей документации (про gulp есть отличная статья на нашем сайте — прим. переводчика).

Заключение

Асинхронные функции ES7 позволяют разработчикам избежать ада коллбэков так, как не могут обещания сами по себе. Эта новая особенность языка делает асинхронный код очень похожим на синхронный. Благодаря компиляции уже сегодня можно использовать асинхронные функции. Так чего же вы ждете? Идите и сделайте свой код потрясающим!

Нашли опечатку? Orphus: Ctrl+Enter

© getinstance.info Все права защищены. 2014–2018

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

Основы построения асинхронных приложений

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

Базовое описание асинхронного кода

Основными функциями асинхронного кода JavaScript являются setTimeout и setInterval . Функция setTimeout выполняет заданную функцию после истечения определенного временного интервала. Она принимает возвратную функцию в качестве первого аргумента и время (в миллисекундах) в качестве второго аргумента. Вот пример использования:

Ожидается, что в консоли мы увидим “a”, “b”, а затем через примерно 500 мс — “c”, “d”, и “e”. Я использую термин “примерно” потому, что в действительности setTimeout работает непредсказуемо. Даже в спецификации HTML5 указано: «Таким образом, API не гарантирует, что таймер выполнится точно по заданному расписанию. Вероятны задержки из-за нагрузки процессора, других задач и прочих факторов.»

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

Цикл событий очередь возвратных функций. Когда асинхронная функция выполняется, возвратная функция ставится в очередь. JavaScript не запускает обработку цикла событий, пока код, запущенный после асинхронной функции выполняется. Данный факт означает, что код JavaScript не является многопоточным, хотя и кажется таковым. Цикл событий является очередью FIFO (первый пришел, первый вышел), что означает выполнение возвратных функций в порядке их поступления. JavaScript был выбран для платформы node.js именно по причине простого процесса разработки подобного кода.

Асинхронный JavaScript и XML (AJAX) навсегда изменил профиль JavaScript. Браузер может обновлять веб страницу без перезагрузки. Код реализации AJAX в разных браузерах может оказаться длинным и занудным. Но, благодаря jQuery (и другим библиотекам), AJAX стал очень простым и элегантным решением для обеспечения клиент-серверных коммуникаций.

Асинхронное получение данных с помощью метода jQuery $.ajax является простым кросс-браузерным процессом, который скрывает реальный процесс. Например:

Обычно, но неправильно, предполагается, что данные станут доступны сразу после вызова $.ajax . Но действительность выглядит иначе:

Объект XmlHttpRequest (xmlhttp) отправляет запрос, а возвратная функция обрабатывает событие объекта readystatechange . Затем xmlhttp выполняет метод send . По мере работы xmlhttp, событие readystatechange генерируется каждый раз при изменении свойства readyState , и только когда xmlhttp закончит получение ответа от удаленного хоста, будет выполняться возвратная функция.

Работа с асинхронным кодом

Асинхронное программирование часто называют «адом возвратных функций». Потому, что теоретически все асинхронные функции JavaScript используют возвратные функции, выполнение множественных последовательных асинхронных функций в результате приводят к появлению вложенных возвратных функций, что очень затрудняет чтение кода.

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

А на клиентской стороне можно встретить такой код:

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

Именованные функции

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

Дополнительно библиотека async.js может помочь обработать множественные запросы/ответы AJAX. Например:

Данный код выполняет две асинхронные функции, и каждая функция принимает возвратную функцию “done”, которая выполняется после завершения асинхронной функции. Когда обе возвратных функции “done” завершатся, возвратная функция для parallel выполнится для обработки всех ошибок или результатов обеих асинхронных функций.

Обещание (promise)

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

Существует много библиотек, которые имеют шаблон обещаний. jQuery использует отличный API обещаний. Объект Deferred появился в версии 1.5, и конструктор jQuery.Deferred используется в функциях, которые возвращают обещания. Функции, которые возвращают обещания, выполняют некоторый вид асинхронных операций.

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

В данном коде метод geocode выполняется дважды и возвращает обещание. Затем выполняется асинхронная функция и вызывает resolve в своей возвратной функции. Затем, как только проходят оба вызова resolve , выполняется then , возвращающий результат первых двух вызовов geocode . Результаты передаются в getRoute , который также возвращает обещание. В завершение, когда обещание из getRoute разрешается, выполняется возвратная функция doSomethingCoolWithDirections .

Читать еще:  С язык программирования учебник

События

События являются другим решением для связи, когда асинхронные возвратные функции заканчивают выполнение. Объект может стать эмиттером и генерировать событие, которое будет отлавливать другой объект. Такой тип работы с событиями называется шаблоном наблюдения (observer pattern). Библиотека backbone.js использует такой функционал с помощью Backbone.Events .

Существуют другие библиотеки, использующие такую модель работы с событиями, такие как jQuery Event Emitter, EventEmitter, monologue.js и node.js имеет модуль EventEmitter.

Схожий метод публикации событий используется в шаблоне с посредником (mediator pattern), который применяется в библиотеке postal.js. В шаблоне с посредником имеется доступный для всех объектов посредник, который ловит и публикует события. При таком подходе один объект не имеет прямых ссылок на другой объект, а следовательно, все объекты отвязаны друг от друга.

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

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

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

Другие реализации шаблона с посредником используются в библиотеках amplify, PubSubJS и radio.js.

Заключение

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

5 последних уроков рубрики «Разное»

Как разместить свой сайт на хостинге? Правильно выбранный хороший хостинг — это будущее Ваших сайтов

Проект готов, Все проверено на локальном сервере OpenServer и можно переносить сайт на хостинг. Вот только какую компанию выбрать? Предлагаю рассмотреть хостинг fornex.com. Отличное место для твоего проекта с перспективами бурного роста.

Разработка веб-сайтов с помощью онлайн платформы Wrike

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

20 ресурсов для прототипирования

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

Топ 10 бесплатных хостингов

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

Быстрая заметка: массовый UPDATE в MySQL

Ни для кого не секрет как в MySQL реализовать массовый INSERT, а вот с UPDATE-ом могут возникнуть сложности. Чтобы не прибегать к манипуляциям события ON_DUPLICATE можно воспользоваться специальной конструкцией CASE … WHEN … THEN.

ПОНИМАНИЕ АСИНХРОННОГО ПРОГРАММИРОВАНИЯ (Understanding Async Programming in Node.js)

Last updated: Oct 19, 2017

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

RisingStack’s services:

Sign up to our newsletter!

Join 150K+ monthly readers.

In-depth articles on Node.js, Microservices, Kubernetes and DevOps.

In this article:

Co-Founder of RisingStack, EM at Uber

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

This article was translated to Russian by Andrey Melikhov, a front-end developer from Yandex.Money and editor of the collective blog about front-end, devSchacht. Find Andrey on: Twitter, GitHub, Medium & SoundCloud

Read the original article in English: Understanding Async Programming in Node.js.

Перевод этой статьи сделан Андреем Мелиховым, фронтенд-разработчиком из компании Яндекс.Деньги, редактором коллективного блога о фронтенде, devSchacht. Twitter | GitHub | Medium | SoundCloud

Синхронное программирование

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

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

Если у вас больше блокирующих операций, очередь событий становится ещё хуже:

(Красные полосы отображают промежутки времени, в которые процесс ожидает ответа от внешнего ресурса и блокируется, чёрные полосы показывают, когда ваш код работает, зелёные полосы отображают остальную часть приложения)

Для решения этой проблемы Node.js предлагает модель асинхронного программирования.

Асинхронное программирование в Node.js

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

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

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

Что здесь происходит? Мы читаем файл, используя синхронный интерфейс модуля fs . Он работает ожидаемым образом: в переменную content сохраняется содержимое file.md . Проблема с этим подходом заключается в том, что Node.js будет заблокирована до завершения операции, то есть, пока читается файл, она не может сделать ничего полезного.

Посмотрим, как мы можем это исправить!

Асинхронное программирование, в том виде, в каком мы знаем его в JavaScript, может быть реализовано только при условии, что функции являются объектами первого класса: они могут передаваться как любые другие переменные другим функциям. Функции, которые могут принимать другие функции в качестве аргументов, называются функциями высшего порядка.

Один из самых простых примеров функций высшего порядка:

В приведённом выше примере мы передаём функцию isBiggerThanTwo в функцию filter . Таким образом, мы можем определить логику фильтрации.

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

В основе Node.js лежит принцип «первым аргументом в колбеке должна быть ошибка». Его придерживаются базовые модули, а также большинство модулей, найденных в NPM.

Что следует здесь выделить:

  • обработка ошибок: вместо блока try-catch вы проверяете ошибку в колбеке
  • отсутствует возвращаемое значение: асинхронные функции не возвращают значения, но значения будут переданы в колбеки

Давайте немного изменим этот файл, чтобы увидеть, как это работает на практике:

Результатом выполнения этого кода будет:

Как вы можете видеть, как только мы начали читать наш файл, выполнение кода продолжилось, а приложение вывело end of the file . Наш колбек вызвался только после завершения чтения файла. Как такое возможно? Встречайте цикл событий (event loop).

Цикл событий

Цикл событий лежит в основе Node.js и JavaScript и отвечает за планирование асинхронных операций.

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

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

На практике это означает, что приложения реагируют на события.

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

Для более глубокого понимания работы цикла событий рекомендуем посмотреть это видео:

Асинхронный поток управления

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

Async.js

Чтобы избежать так называемого Callback-Hell, вы можете начать использовать async.js.

Async.js помогает структурировать ваши приложения и упрощает понимание потока управления.

Давайте рассмотрим короткий пример использования Async.js, а затем перепишем его с помощью промисов.

Следующий фрагмент перебирает три файла и выводит системную информацию по каждому:

Примечание переводчика: если вы пользуетесь Node.js версии 7 и выше, лучше воспользоваться встроенными конструкциями языка, такими как async/await.

Промисы

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

На практике предыдущий пример можно переписать следующим образом:

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

Ссылка на основную публикацию
Adblock
detector