Синхронный и асинхронный код счетчика Яндекс.Метрика. Коды (асинхронный и синхронный): формирование и установка

10.05.2019

Объявлений переменных и функций, но чтобы превратить эту особенность JS в проблему, надо очень постараться. У синхронного кода на JavaScript есть лишь один серьёзный недостаток: на нём одном далеко не уехать.

Практически каждая полезная JS-программа написана с привлечением асинхронных методов разработки. Здесь в дело вступают функции обратного вызова, в просторечии - «коллбэки». Здесь в ходу «обещания», или Promise-объекты, называемые обычно промисами. Тут можно столкнуться с генераторами и с конструкциями async/await. Асинхронный код, в сравнении с синхронным, обычно сложнее писать, читать и поддерживать. Иногда он превращается в совершенно жуткие структуры вроде ада коллбэков. Однако, без него не обойтись.

Сегодня предлагаем поговорить об особенностях коллбэков, промисов, генераторов и конструкций async/await, и подумать о том, как писать простой, понятный и эффективный асинхронный код.

О синхронном и асинхронном коде

Начнём с рассмотрения фрагментов синхронного и асинхронного JS-кода. Вот, например, обычный синхронный код:

Console.log("1") console.log("2") console.log("3")
Он, без особых сложностей, выводит в консоль числа от 1 до 3.

Теперь - код асинхронный:

Console.log("1") setTimeout(function afterTwoSeconds() { console.log("2") }, 2000) console.log("3")
Тут уже будет выведена последовательность 1, 3, 2. Число 2 выводится из коллбэка, который обрабатывает событие срабатывания таймера, заданного при вызове функции setTimeout . Коллбэк будет вызвана, в данном примере, через 2 секунды. Приложение при этом не остановится, ожидая, пока истекут эти две секунды. Вместо этого его исполнение продолжится, а когда сработает таймер, будет вызвана функция afterTwoSeconds .

Возможно, если вы только начинаете путь JS-разработчика, вы зададитесь вопросами: «Зачем это всё? Может быть, можно переделать асинхронный код в синхронный?». Поищем ответы на эти вопросы.

Постановка задачи

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

В плане интерфейса ограничимся чем-нибудь простым .


Простой интерфейс поиска пользователей GitHub и соответствующих им репозиториев

В примерах выполнение запросов будет выполнено средствами XMLHttpRequest (XHR), но вы вполне можете использовать тут jQuery ($.ajax), или более современный стандартный подход, основанный на использовании функции fetch . И то и другое сводится к использованию промисов. Код, в зависимости от похода, будет меняться, но вот, для начала, такой пример:

// аргумент url может быть чем-то вроде "https://api.github.com/users/daspinola/repos" function request(url) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { // Код обработки успешного завершения запроса } else { // Обрабатываем ответ с сообщением об ошибке } } } xhr.ontimeout = function () { // Ожидание ответа заняло слишком много времени, тут будет код, который обрабатывает подобную ситуацию } xhr.open("get", url, true) xhr.send(); }
Обратите внимание на то, что в этих примерах важно не то, что в итоге придёт с сервера, и как это будет обработано, а сама организация кода при использовании разных подходов, которые вы сможете использовать в своих асинхронных разработках.

Функции обратного вызова

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

// Вызовем функцию "doThis" с другой функцией в качестве параметра, в данном случае - это функция "andThenThis". Функция "doThis" исполнит код, находящийся в ней, после чего, в нужный момент, вызовет функцию "andThenThis". doThis(andThenThis) // Внутри "doThis" обращение к переданной ей функции осуществляется через параметр "callback" , фактически, это просто переменная, которая хранит ссылку на функцию function andThenThis() { console.log("and then this") } // Назвать параметр, в котором окажется функция обратного вызова, можно как угодно, "callback" - это просто распространённый вариант function doThis(callback) { console.log("this first") // Для того, чтобы функция, ссылка на которую хранится в переменной, была вызвана, нужно поместить после имени переменной скобки, "()", иначе ничего не получится callback() }
Используя этот подход для решения нашей задачи, мы можем написать такую функцию request:

Function request(url, callback) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.response) } else { callback(xhr.status, null) } } } xhr.ontimeout = function () { console.log("Timeout") } xhr.open("get", url, true) xhr.send(); }
Теперь функция для выполнения запроса принимает параметр callback , поэтому, после выполнения запроса и получения ответа сервера, коллбэк будет вызван и в случае ошибки, и в случае успешного завершения операции.

Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` request(userGet, function handleUsersList(error, users) { if (error) throw error const list = JSON.parse(users).items list.forEach(function(user) { request(user.repos_url, function handleReposList(err, repos) { if (err) throw err //Здесь обработаем список репозиториев }) }) })
Разберём то, что здесь происходит:

  • Выполняется запрос для получения репозиториев пользователя (в данном случае я загружаю собственные репозитории);
  • После завершения запроса вызывается коллбэк handleUsersList ;
  • Если не было ошибок, разбираем ответ сервера c помощью J SON.parse , преобразовываем его, для удобства, в объект;
  • После этого перебираем список пользователей, так как в нём может быть больше одного элемента, и для каждого из них запрашиваем список репозиториев, используя URL, возвращённый для каждого пользователя после выполнения первого запроса. Подразумевается, что repos_url - это URL для наших следующих запросов, и получили мы его из первого запроса.
  • Когда запрос, направленный на загрузку данных о репозиториях, завершён, вызывается коллбэк, теперь это handleReposList . Здесь, так же как и при загрузке списка пользователей, можно обработать ошибки или полезные данные, в которых содержится список репозиториев пользователя.
Обратите внимание на то, что использование в качестве первого параметра объекта ошибки - это широко распространённая практика, в частности, для разработки с использованием Node.js.

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

Try { request(userGet, handleUsersList) } catch (e) { console.error("Request boom! ", e) } function handleUsersList(error, users) { if (error) throw error const list = JSON.parse(users).items list.forEach(function(user) { request(user.repos_url, handleReposList) }) } function handleReposList(err, repos) { if (err) throw err // Здесь обрабатываем список репозиториев console.log("My very few repos", repos) }
Этот подход работает, но используя его, мы рискуем столкнуться с проблемами вроде состояния гонки запросов и сложностей с обработкой ошибок. Однако, основная неприятность, связанная с коллбэками, которых, считая то, что происходит в цикле forEach , здесь три, заключается в том, что такой код тяжело читать и поддерживать. Подобная проблема существует, пожалуй, со дня появления функций обратного вызова, она широко известна как ад коллбэков.


Ад коллбэков во всей красе. Изображение взято отсюда .

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

Промисы

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

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

Const myPromise = new Promise(function(resolve, reject) { // Здесь будет код if (codeIsFine) { resolve("fine") } else { reject("error") } }) myPromise .then(function whenOk(response) { console.log(response) return response }) .catch(function notOk(err) { console.error(err) })
Разберём этот пример:

  • Промис инициализируется с помощью функции, в которой есть вызовы методов resolve и reject ;
  • Асинхронный код помещают внутри функции, созданной с помощью конструктора Promise . Если код будет выполнен успешно, вызывают метод resolve , если нет - reject ;
  • Если функция вызовет resolve , будет исполнен метод.then для объекта Promise , аналогично, если будет вызван reject , будет исполнен метод.catch .
Вот что стоит помнить, работая с промисами:
  • Методы resolve и reject принимают только один параметр, в результате, например, при выполнении команды вида resolve("yey", "works") , коллбэку.then будет передано лишь "yey" ;
  • Если объединить в цепочку несколько вызовов.then , в конце соответствующих коллбэков следует всегда использовать return , иначе все они будут выполнены одновременно, а это, очевидно, не то, чего вы хотите достичь;
  • При выполнении команды reject , если следующим в цепочке идёт.then , он будет выполнен (вы можете считать.then выражением, которое выполняется в любом случае);
  • Если в цепочке из вызовов.then в каком-то из них возникнет ошибка, следующие за ним будут пропущены до тех пор, пока не будет найдено выражение.catch ;
  • У промисов есть три состояния: «pending» - состояние ожидания вызова resolve или reject , а также состояния «resolved» и «rejected», которые соответствуют успешному, с вызовом resolve , и неуспешному, с вызовом reject , завершению работы промиса. Когда промис оказывается в состоянии «resolved» или «rejected», оно уже не может быть изменено.
Обратите внимание на то, что промисы можно создавать без использования отдельно определённых функций, описывая функции в момент создания промисов. То, что показано в нашем примере - лишь распространённый способ инициализации промисов.

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

Function request(url) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(xhr.response) } else { reject(xhr.status) } } } xhr.ontimeout = function () { reject("timeout") } xhr.open("get", url, true) xhr.send(); }) }
При таком подходе, когда вы вызываете request , возвращено будет примерно следующее.

Это - промис в состоянии ожидания. Он может быть либо успешно разрешён, либо отклонён

Теперь, воспользовавшись новой функцией request , перепишем остальной код.

Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const myPromise = request(userGet) console.log("will be pending when logged", myPromise) myPromise .then(function handleUsersList(users) { console.log("when resolve is found it comes here with the response, in this case users ", users) const list = JSON.parse(users).items return Promise.all(list.map(function(user) { return request(user.repos_url) })) }) .then(function handleReposList(repos) { console.log("All users repos in an array", repos) }) .catch(function handleErrors(error) { console.log("when a reject is executed it will come here ignoring the then statement ", error) })
Здесь мы оказываемся в первом выражении.then при успешном разрешении промиса. У нас имеется список пользователей. Во второе выражение.then мы передаём массив с репозиториями. Если что-то пошло не так, мы окажемся в выражении.catch .

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

Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const userRequest = request(userGet) // Если просто прочитать эту часть программы вслух, можно сразу понять что именно делает код userRequest .then(handleUsersList) .then(repoRequest) .then(handleReposList) .catch(handleErrors) function handleUsersList(users) { return JSON.parse(users).items } function repoRequest(users) { return Promise.all(users.map(function(user) { return request(user.repos_url) })) } function handleReposList(repos) { console.log("All users repos in an array", repos) } function handleErrors(error) { console.error("Something went wrong ", error) }
При таком подходе один взгляд на имена коллбэков в выражениях.then раскрывает смысл вызова userRequest . С кодом легко работать, его легко читать.

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

Генераторы

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

Для того, чтобы определить функцию-генератор, можно воспользоваться знаком звёздочки, «*», после ключевого слова function . С помощью генераторов асинхронный код можно сделать очень похожим на синхронный. Например, выглядеть это может так:

Function* foo() { yield 1 const args = yield 2 console.log(args) } var fooIterator = foo() console.log(fooIterator.next().value) // выведет 1 console.log(fooIterator.next().value) // выведет 2 fooIterator.next("aParam") // приведёт к вызову console.log внутри генератора и к выводу "aParam"
Дело тут в том, что генераторы, вместо return , используют выражение yield , которое останавливает выполнение функции до следующего вызова.next итератора. Это похоже на выражение.then в промисах, которое выполняется при разрешении промиса.

Посмотрим теперь, как это всё применить к нашей задаче. Итак, вот функция request:

Function request(url) { return function(callback) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.response) } else { callback(xhr.status, null) } } } xhr.ontimeout = function () { console.log("timeout") } xhr.open("get", url, true) xhr.send() } }
Тут, как обычно, мы используем аргумент url , но вместо того, чтобы сразу выполнить запрос, мы хотим его выполнить только тогда, когда у нас будет функция обратного вызова для обработки ответа.

Генератор будет выглядеть так:

Function* list() { const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const users = yield request(userGet) yield for (let i = 0; i<=users.length; i++) { yield request(users[i].repos_url) } }
Вот что здесь происходит:

  • Мы ожидаем подготовки первого запроса, возвращая ссылку на функцию и ожидая коллбэка для этого первого запроса (вспомните функцию request , которая она принимает url и возвращает функцию, которая ожидает коллбэк);
  • Ожидаем готовности списка пользователей, users , для отправки в следующий.next ;
  • Проходимся по полученному массиву users и ожидаем, для каждого из них, .next , возвращая, для каждого, соответствующий коллбэк.
Использование этого всего будет выглядеть так:

Try { const iterator = list() iterator.next().value(function handleUsersList(err, users) { if (err) throw err const list = JSON.parse(users).items // Отправляем список пользователей итератору iterator.next(list) list.forEach(function(user) { iterator.next().value(function userRepos(error, repos) { if (error) throw repos // Здесь обрабатываем информацию о репозиториях каждого пользователя console.log(user, JSON.parse(repos)) }) }) }) } catch (e) { console.error(e) }
Здесь мы можем индивидуально обрабатывать список репозиториев каждого пользователя. Для того, чтобы улучшить этот код, можно было бы выделить функции обратного вызова, как мы уже делали выше.

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

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

Async/await

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

SumTwentyAfterTwoSeconds(10) .then(result => console.log("after 2 seconds", result)) async function sumTwentyAfterTwoSeconds(value) { const remainder = afterTwoSeconds(20) return value + await remainder } function afterTwoSeconds(value) { return new Promise(resolve => { setTimeout(() => { resolve(value) }, 2000); }); }
Здесь происходит следующее:

  • Имеется асинхронная функция sumTwentyAfterTwoSeconds ;
  • Мы предлагаем коду подождать разрешения промиса afterTwoSeconds , который может завершиться вызовом resolve или reject ;
  • Выполнение кода заканчивается в.then , где завершается операция, отмеченная ключевым словом await , в данном случае - это всего одна операция.
Подготовим функцию request к использовании в конструкции async/await:

Function request(url) { return new Promise(function(resolve, reject) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(xhr.response) } else { reject(xhr.status) } } } xhr.ontimeout = function () { reject("timeout") } xhr.open("get", url, true) xhr.send() }) }
Теперь создаём функцию с ключевым словом async , в которой используем ключевое слово await:

Async function list() { const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const users = await request(userGet) const usersList = JSON.parse(users).items usersList.forEach(async function (user) { const repos = await request(user.repos_url) handleRepoList(user, repos) }) } function handleRepoList(user, repos) { const userRepos = JSON.parse(repos) // Обрабатываем тут репозитории для каждого пользователя console.log(user, userRepos) }
Итак, у нас имеется асинхронная функция list , которая обработает запрос. Ещё конструкция async/await нам понадобится в цикле forEach , чтобы сформировать список репозиториев. Вызвать всё это очень просто:

List() .catch(e => console.error(e))
Этот подход и использование промисов - мои любимые методы асинхронного программирования. Код, написанный с их использованием, удобно и читать и править. Подробности об async/await можно почитать .

Минус async/await , как и минус генераторов, заключается в том, что эту конструкцию не поддерживают старые браузеры, а для её использования в серверной разработке нужно пользоваться Node 8. В подобной ситуации, опять же, поможет транспилятор, например - babel .

Итоги

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

Обратите внимание на то, что наши примеры можно улучшить, сделать лаконичнее, если переписать их с использованием альтернативных способов выполнения запросов, вроде $.ajax и fetch . Если у вас есть идеи о том, как улучшить качество кода при использовании вышеописанных методик - буду благодарен, если расскажете об этом мне .

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

Уважаемые читатели! Какими методиками написания асинхронного кода на JavaScript вы пользуетесь?

Теги:

  • JavaScript
  • разработка
  • callback
  • async
  • await
  • promise
  • generator
  • асинхронный код
Добавить метки

Коды (асинхронный и синхронный): формирование и установка

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

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

На текущий момент Google Analytics предлагает асинхронную версию кода, которая обладает рядом преимуществ относительно стандартного кода.

Асинхронный код – это улучшенный фрагмент кода JavaScript, который загружает код отслеживания ga.js в фоновом режиме, без приостановки загрузки других скриптов и контента на страницах вашего веб-сайта.

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

  • Более точный сбор данных о коротких посещениях
  • Если клики произошли до загрузки кода, данные будут сохранены
  • Если у вас установлена стандартная версия кода, рекомендуется обновить код на асинхронный. Для этого необходимо удалить старый фрагмент кода, и на его место встроить асинхронный код. После установки асинхронного кода данные начнут появляться в аккаунте в течение 24 часов.

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

    Формирование и установка кода

    1. Код создается на уровне профиля. Необходимо перейти на вкладку «Администратор».

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

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

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

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

    Отзывчивость

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

    Параллельность vs асинхронность

    Большинство людей, когда они видят асинхронный код, сразу думают “О, это круто! Я могу запустить мои задачи параллельно!” . Я, может вас разочарую, но на самом деле, это неправда, конкурентность (асинхронность) и параллельность — это не то же самое. Это часто встречающаяся ошибка, поэтому давайте попробуем разобраться почему.

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

    Асинхронность:

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

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

    Параллельность:

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

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

    Nginx работает иначе, чем Apache и не создаёт новые потоки для каждого входящего запроса. У него есть основной процесс-воркер (или несколько процессов, зачастую на практике рекомендуется иметь по одному воркеру на каждый процессор(CPU)), который является однопоточным. Этот воркер может обрабатывать тысячи одновременных подключений. Он делает это асинхронно с одним потоком, а не с помощью многопоточного параллельного выполнения.

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

    От переводчика:

    Асинхронность : у вас есть бекенд задача, которую вы делаете в данный момент. Тут к вам обратился фронтенд разработчик с просьбой немого поправить API. Вы сохранились у себя в ветке, переключились на другую (в данный момент вы заняты задачей с API), поправили и вернулись к своей задаче. После вас позвали на совещание по какой-то тематике, вы прервались, сходили (в данный момент вы выполняете третью задачу, которая будет продолжена на следующем совещании) и вернулись опять к своей первой задаче. Эти три задачи вы выполняли асинхронно — другими словами, прерываясь и переключаясь между задачами, выделяя на них понемногу времени.

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

    Зачем на бекенде?

    Теперь вы можете возмутиться на то, что на беке вас не особо волнует отзывчивость. У вас на фронте есть все эти гадкие вещи типа асинхронного JavaScript-а, а всё, что должен делать ваш сервер, — это просто отвечать на запросы. Так что обеспечить отзывчивость пользователю — это задача фронта, а не ваша. Да, это правда, но бекенд не ограничивается только API-ответами. Иногда вам нужно управлять какими-то сложными задачами, например, сервер для загрузки видео. В данном случае, возможно, отклик не является ключевым фактором, но мы упираемся в нехватку ресурсов , потому что приложение должно ждать. Он может ждать операций файловой системы, сетевого подключения, запросов к базе и так далее. Часто эти операции ввода/вывода осуществляются крайне медленно по сравнению с расчетами на CPU, например, когда мы конвертируем видеофайлы. А пока мы медленно сохраняем или читаем файл, наш процессор должен ждать и ничего не делать, вместо того, чтобы делать какую-то полезную работу. Как мы уже говорили, вместо ожидания, мы можем выполнять эти задачи в фоновом режиме. Как? Читайте ниже.

    Асинхронный РНР

    JavaScript мир уже имеет встроенную поддержку и инструменты для написания асинхронного кода. И также есть NodeJs, который позволяет писать асинхронные приложения. В JavaScript, мы можем использовать функцию setTimeout() , чтобы продемонстрировать асинхронный код:

    SetTimeout(function() { console.log("After timeout"); }, 1); console.log("Before timeout");

    При выполнении этого кода мы видим следующее:

    Before timeout After timeout

    Функция setTimeout() ставит в очередь код, который будет выполняться после завершения текущего стека вызовов. Это означает, что мы нарушаем синхронный поток кода и откладываем выполнение некоторой части кода. Второй вызов console.log() будет выполнен до первого (внутри функции settimeout()), который был поставлен в очередь.

    Но как на счёт PHP? Ну, в PHP из коробки у нас нет хороших и удобных инструментов для написания действительно асинхронного кода. Нет функции эквивалентной settimeout() и мы просто не можем отложить или поставить в очередь какой-то код. Вот почему такие фреймворки и библиотеки, как Amp и ReactPHP , начали появляться. Их основная идея в том, чтобы скрыть от нас низкоуровневые тонкости языка и предоставить инструменты и абстракции высокого уровня, которые могут быть использованы для написания асинхронного кода и управления конкурентностью, как мы могли бы сделать это в JavaScript и NodeJS.

    Почему я должен использовать PHP, если у нас есть NodeJs и Go?

    Подобный вопрос возникает чаще всего, когда речь идет об асинхронном РНР. Почему-то сообщество часто против использования PHP в качестве инструмента для написания асинхронного кода. Всегда кто-то предлагает просто использовать Go и NodeJs .

    Объявлений переменных и функций, но чтобы превратить эту особенность JS в проблему, надо очень постараться. У синхронного кода на JavaScript есть лишь один серьёзный недостаток: на нём одном далеко не уехать.

    Практически каждая полезная JS-программа написана с привлечением асинхронных методов разработки. Здесь в дело вступают функции обратного вызова, в просторечии - «коллбэки». Здесь в ходу «обещания», или Promise-объекты, называемые обычно промисами. Тут можно столкнуться с генераторами и с конструкциями async/await. Асинхронный код, в сравнении с синхронным, обычно сложнее писать, читать и поддерживать. Иногда он превращается в совершенно жуткие структуры вроде ада коллбэков. Однако, без него не обойтись.

    Сегодня предлагаем поговорить об особенностях коллбэков, промисов, генераторов и конструкций async/await, и подумать о том, как писать простой, понятный и эффективный асинхронный код.

    О синхронном и асинхронном коде

    Начнём с рассмотрения фрагментов синхронного и асинхронного JS-кода. Вот, например, обычный синхронный код:

    Console.log("1") console.log("2") console.log("3")
    Он, без особых сложностей, выводит в консоль числа от 1 до 3.

    Теперь - код асинхронный:

    Console.log("1") setTimeout(function afterTwoSeconds() { console.log("2") }, 2000) console.log("3")
    Тут уже будет выведена последовательность 1, 3, 2. Число 2 выводится из коллбэка, который обрабатывает событие срабатывания таймера, заданного при вызове функции setTimeout . Коллбэк будет вызвана, в данном примере, через 2 секунды. Приложение при этом не остановится, ожидая, пока истекут эти две секунды. Вместо этого его исполнение продолжится, а когда сработает таймер, будет вызвана функция afterTwoSeconds .

    Возможно, если вы только начинаете путь JS-разработчика, вы зададитесь вопросами: «Зачем это всё? Может быть, можно переделать асинхронный код в синхронный?». Поищем ответы на эти вопросы.

    Постановка задачи

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

    В плане интерфейса ограничимся чем-нибудь простым .


    Простой интерфейс поиска пользователей GitHub и соответствующих им репозиториев

    В примерах выполнение запросов будет выполнено средствами XMLHttpRequest (XHR), но вы вполне можете использовать тут jQuery ($.ajax), или более современный стандартный подход, основанный на использовании функции fetch . И то и другое сводится к использованию промисов. Код, в зависимости от похода, будет меняться, но вот, для начала, такой пример:

    // аргумент url может быть чем-то вроде "https://api.github.com/users/daspinola/repos" function request(url) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { // Код обработки успешного завершения запроса } else { // Обрабатываем ответ с сообщением об ошибке } } } xhr.ontimeout = function () { // Ожидание ответа заняло слишком много времени, тут будет код, который обрабатывает подобную ситуацию } xhr.open("get", url, true) xhr.send(); }
    Обратите внимание на то, что в этих примерах важно не то, что в итоге придёт с сервера, и как это будет обработано, а сама организация кода при использовании разных подходов, которые вы сможете использовать в своих асинхронных разработках.

    Функции обратного вызова

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

    // Вызовем функцию "doThis" с другой функцией в качестве параметра, в данном случае - это функция "andThenThis". Функция "doThis" исполнит код, находящийся в ней, после чего, в нужный момент, вызовет функцию "andThenThis". doThis(andThenThis) // Внутри "doThis" обращение к переданной ей функции осуществляется через параметр "callback" , фактически, это просто переменная, которая хранит ссылку на функцию function andThenThis() { console.log("and then this") } // Назвать параметр, в котором окажется функция обратного вызова, можно как угодно, "callback" - это просто распространённый вариант function doThis(callback) { console.log("this first") // Для того, чтобы функция, ссылка на которую хранится в переменной, была вызвана, нужно поместить после имени переменной скобки, "()", иначе ничего не получится callback() }
    Используя этот подход для решения нашей задачи, мы можем написать такую функцию request:

    Function request(url, callback) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.response) } else { callback(xhr.status, null) } } } xhr.ontimeout = function () { console.log("Timeout") } xhr.open("get", url, true) xhr.send(); }
    Теперь функция для выполнения запроса принимает параметр callback , поэтому, после выполнения запроса и получения ответа сервера, коллбэк будет вызван и в случае ошибки, и в случае успешного завершения операции.

    Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` request(userGet, function handleUsersList(error, users) { if (error) throw error const list = JSON.parse(users).items list.forEach(function(user) { request(user.repos_url, function handleReposList(err, repos) { if (err) throw err //Здесь обработаем список репозиториев }) }) })
    Разберём то, что здесь происходит:

    • Выполняется запрос для получения репозиториев пользователя (в данном случае я загружаю собственные репозитории);
    • После завершения запроса вызывается коллбэк handleUsersList ;
    • Если не было ошибок, разбираем ответ сервера c помощью J SON.parse , преобразовываем его, для удобства, в объект;
    • После этого перебираем список пользователей, так как в нём может быть больше одного элемента, и для каждого из них запрашиваем список репозиториев, используя URL, возвращённый для каждого пользователя после выполнения первого запроса. Подразумевается, что repos_url - это URL для наших следующих запросов, и получили мы его из первого запроса.
    • Когда запрос, направленный на загрузку данных о репозиториях, завершён, вызывается коллбэк, теперь это handleReposList . Здесь, так же как и при загрузке списка пользователей, можно обработать ошибки или полезные данные, в которых содержится список репозиториев пользователя.
    Обратите внимание на то, что использование в качестве первого параметра объекта ошибки - это широко распространённая практика, в частности, для разработки с использованием Node.js.

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

    Try { request(userGet, handleUsersList) } catch (e) { console.error("Request boom! ", e) } function handleUsersList(error, users) { if (error) throw error const list = JSON.parse(users).items list.forEach(function(user) { request(user.repos_url, handleReposList) }) } function handleReposList(err, repos) { if (err) throw err // Здесь обрабатываем список репозиториев console.log("My very few repos", repos) }
    Этот подход работает, но используя его, мы рискуем столкнуться с проблемами вроде состояния гонки запросов и сложностей с обработкой ошибок. Однако, основная неприятность, связанная с коллбэками, которых, считая то, что происходит в цикле forEach , здесь три, заключается в том, что такой код тяжело читать и поддерживать. Подобная проблема существует, пожалуй, со дня появления функций обратного вызова, она широко известна как ад коллбэков.


    Ад коллбэков во всей красе. Изображение взято отсюда .

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

    Промисы

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

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

    Const myPromise = new Promise(function(resolve, reject) { // Здесь будет код if (codeIsFine) { resolve("fine") } else { reject("error") } }) myPromise .then(function whenOk(response) { console.log(response) return response }) .catch(function notOk(err) { console.error(err) })
    Разберём этот пример:

    • Промис инициализируется с помощью функции, в которой есть вызовы методов resolve и reject ;
    • Асинхронный код помещают внутри функции, созданной с помощью конструктора Promise . Если код будет выполнен успешно, вызывают метод resolve , если нет - reject ;
    • Если функция вызовет resolve , будет исполнен метод.then для объекта Promise , аналогично, если будет вызван reject , будет исполнен метод.catch .
    Вот что стоит помнить, работая с промисами:
    • Методы resolve и reject принимают только один параметр, в результате, например, при выполнении команды вида resolve("yey", "works") , коллбэку.then будет передано лишь "yey" ;
    • Если объединить в цепочку несколько вызовов.then , в конце соответствующих коллбэков следует всегда использовать return , иначе все они будут выполнены одновременно, а это, очевидно, не то, чего вы хотите достичь;
    • При выполнении команды reject , если следующим в цепочке идёт.then , он будет выполнен (вы можете считать.then выражением, которое выполняется в любом случае);
    • Если в цепочке из вызовов.then в каком-то из них возникнет ошибка, следующие за ним будут пропущены до тех пор, пока не будет найдено выражение.catch ;
    • У промисов есть три состояния: «pending» - состояние ожидания вызова resolve или reject , а также состояния «resolved» и «rejected», которые соответствуют успешному, с вызовом resolve , и неуспешному, с вызовом reject , завершению работы промиса. Когда промис оказывается в состоянии «resolved» или «rejected», оно уже не может быть изменено.
    Обратите внимание на то, что промисы можно создавать без использования отдельно определённых функций, описывая функции в момент создания промисов. То, что показано в нашем примере - лишь распространённый способ инициализации промисов.

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

    Function request(url) { return new Promise(function (resolve, reject) { const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(xhr.response) } else { reject(xhr.status) } } } xhr.ontimeout = function () { reject("timeout") } xhr.open("get", url, true) xhr.send(); }) }
    При таком подходе, когда вы вызываете request , возвращено будет примерно следующее.

    Это - промис в состоянии ожидания. Он может быть либо успешно разрешён, либо отклонён

    Теперь, воспользовавшись новой функцией request , перепишем остальной код.

    Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const myPromise = request(userGet) console.log("will be pending when logged", myPromise) myPromise .then(function handleUsersList(users) { console.log("when resolve is found it comes here with the response, in this case users ", users) const list = JSON.parse(users).items return Promise.all(list.map(function(user) { return request(user.repos_url) })) }) .then(function handleReposList(repos) { console.log("All users repos in an array", repos) }) .catch(function handleErrors(error) { console.log("when a reject is executed it will come here ignoring the then statement ", error) })
    Здесь мы оказываемся в первом выражении.then при успешном разрешении промиса. У нас имеется список пользователей. Во второе выражение.then мы передаём массив с репозиториями. Если что-то пошло не так, мы окажемся в выражении.catch .

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

    Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const userRequest = request(userGet) // Если просто прочитать эту часть программы вслух, можно сразу понять что именно делает код userRequest .then(handleUsersList) .then(repoRequest) .then(handleReposList) .catch(handleErrors) function handleUsersList(users) { return JSON.parse(users).items } function repoRequest(users) { return Promise.all(users.map(function(user) { return request(user.repos_url) })) } function handleReposList(repos) { console.log("All users repos in an array", repos) } function handleErrors(error) { console.error("Something went wrong ", error) }
    При таком подходе один взгляд на имена коллбэков в выражениях.then раскрывает смысл вызова userRequest . С кодом легко работать, его легко читать.

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

    Генераторы

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

    Для того, чтобы определить функцию-генератор, можно воспользоваться знаком звёздочки, «*», после ключевого слова function . С помощью генераторов асинхронный код можно сделать очень похожим на синхронный. Например, выглядеть это может так:

    Function* foo() { yield 1 const args = yield 2 console.log(args) } var fooIterator = foo() console.log(fooIterator.next().value) // выведет 1 console.log(fooIterator.next().value) // выведет 2 fooIterator.next("aParam") // приведёт к вызову console.log внутри генератора и к выводу "aParam"
    Дело тут в том, что генераторы, вместо return , используют выражение yield , которое останавливает выполнение функции до следующего вызова.next итератора. Это похоже на выражение.then в промисах, которое выполняется при разрешении промиса.

    Посмотрим теперь, как это всё применить к нашей задаче. Итак, вот функция request:

    Function request(url) { return function(callback) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { callback(null, xhr.response) } else { callback(xhr.status, null) } } } xhr.ontimeout = function () { console.log("timeout") } xhr.open("get", url, true) xhr.send() } }
    Тут, как обычно, мы используем аргумент url , но вместо того, чтобы сразу выполнить запрос, мы хотим его выполнить только тогда, когда у нас будет функция обратного вызова для обработки ответа.

    Генератор будет выглядеть так:

    Function* list() { const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const users = yield request(userGet) yield for (let i = 0; i<=users.length; i++) { yield request(users[i].repos_url) } }
    Вот что здесь происходит:

    • Мы ожидаем подготовки первого запроса, возвращая ссылку на функцию и ожидая коллбэка для этого первого запроса (вспомните функцию request , которая она принимает url и возвращает функцию, которая ожидает коллбэк);
    • Ожидаем готовности списка пользователей, users , для отправки в следующий.next ;
    • Проходимся по полученному массиву users и ожидаем, для каждого из них, .next , возвращая, для каждого, соответствующий коллбэк.
    Использование этого всего будет выглядеть так:

    Try { const iterator = list() iterator.next().value(function handleUsersList(err, users) { if (err) throw err const list = JSON.parse(users).items // Отправляем список пользователей итератору iterator.next(list) list.forEach(function(user) { iterator.next().value(function userRepos(error, repos) { if (error) throw repos // Здесь обрабатываем информацию о репозиториях каждого пользователя console.log(user, JSON.parse(repos)) }) }) }) } catch (e) { console.error(e) }
    Здесь мы можем индивидуально обрабатывать список репозиториев каждого пользователя. Для того, чтобы улучшить этот код, можно было бы выделить функции обратного вызова, как мы уже делали выше.

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

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

    Async/await

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

    SumTwentyAfterTwoSeconds(10) .then(result => console.log("after 2 seconds", result)) async function sumTwentyAfterTwoSeconds(value) { const remainder = afterTwoSeconds(20) return value + await remainder } function afterTwoSeconds(value) { return new Promise(resolve => { setTimeout(() => { resolve(value) }, 2000); }); }
    Здесь происходит следующее:

    • Имеется асинхронная функция sumTwentyAfterTwoSeconds ;
    • Мы предлагаем коду подождать разрешения промиса afterTwoSeconds , который может завершиться вызовом resolve или reject ;
    • Выполнение кода заканчивается в.then , где завершается операция, отмеченная ключевым словом await , в данном случае - это всего одна операция.
    Подготовим функцию request к использовании в конструкции async/await:

    Function request(url) { return new Promise(function(resolve, reject) { const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(e) { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(xhr.response) } else { reject(xhr.status) } } } xhr.ontimeout = function () { reject("timeout") } xhr.open("get", url, true) xhr.send() }) }
    Теперь создаём функцию с ключевым словом async , в которой используем ключевое слово await:

    Async function list() { const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const users = await request(userGet) const usersList = JSON.parse(users).items usersList.forEach(async function (user) { const repos = await request(user.repos_url) handleRepoList(user, repos) }) } function handleRepoList(user, repos) { const userRepos = JSON.parse(repos) // Обрабатываем тут репозитории для каждого пользователя console.log(user, userRepos) }
    Итак, у нас имеется асинхронная функция list , которая обработает запрос. Ещё конструкция async/await нам понадобится в цикле forEach , чтобы сформировать список репозиториев. Вызвать всё это очень просто:

    List() .catch(e => console.error(e))
    Этот подход и использование промисов - мои любимые методы асинхронного программирования. Код, написанный с их использованием, удобно и читать и править. Подробности об async/await можно почитать .

    Минус async/await , как и минус генераторов, заключается в том, что эту конструкцию не поддерживают старые браузеры, а для её использования в серверной разработке нужно пользоваться Node 8. В подобной ситуации, опять же, поможет транспилятор, например - babel .

    Итоги

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

    Обратите внимание на то, что наши примеры можно улучшить, сделать лаконичнее, если переписать их с использованием альтернативных способов выполнения запросов, вроде $.ajax и fetch . Если у вас есть идеи о том, как улучшить качество кода при использовании вышеописанных методик - буду благодарен, если расскажете об этом мне .

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

    Уважаемые читатели! Какими методиками написания асинхронного кода на JavaScript вы пользуетесь?

    Теги:

    Добавить метки

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

    ","contentType":"text/html"},"proposedBody":{"source":"

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

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

    ","contentType":"text/html"},"authorId":"119287251","slug":"457","canEdit":false,"canComment":false,"isBanned":false,"canPublish":false,"viewType":"old","isDraft":false,"isOnModeration":false,"isSubscriber":false,"commentsCount":21,"modificationDate":"Thu Jan 01 1970 03:00:00 GMT+0000 (UTC)","showPreview":true,"approvedPreview":{"source":"

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

    ","html":"Партнеры РСЯ могут ускорить загрузку рекламных блоков на своих сайтах с помощью нового асинхронного кода.","contentType":"text/html"},"proposedPreview":{"source":"

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

    ","html":"Партнеры РСЯ могут ускорить загрузку рекламных блоков на своих сайтах с помощью нового асинхронного кода.","contentType":"text/html"},"titleImage":null,"tags":[{"displayName":"асинхронный код","slug":"asinkhronnyy-kod","categoryId":"47982168","url":"/blog/partner??tag=asinkhronnyy-kod"},{"displayName":"партнерский интерфейс","slug":"partnerskiy-interfeys","categoryId":"25247736","url":"/blog/partner??tag=partnerskiy-interfeys"},{"displayName":"новость","slug":"novost","categoryId":"25247737","url":"/blog/partner??tag=novost"},{"displayName":"рекламные блоки","slug":"reklamnye-bloki","categoryId":"24650991","url":"/blog/partner??tag=reklamnye-bloki"}],"isModerator":false,"commentsEnabled":true,"url":"/blog/partner/457","urlTemplate":"/blog/partner/%slug%","fullBlogUrl":"https://yandex.ru/blog/partner","addCommentUrl":"/blog/createComment/partner/457","updateCommentUrl":"/blog/updateComment/partner/457","addCommentWithCaptcha":"/blog/createWithCaptcha/partner/457","changeCaptchaUrl":"/blog/api/captcha/new","putImageUrl":"/blog/image/put","urlBlog":"/blog/partner","urlEditPost":"/blog/56a93a7dd97351872e7341e0/edit","urlSlug":"/blog/post/generateSlug","urlPublishPost":"/blog/56a93a7dd97351872e7341e0/publish","urlUnpublishPost":"/blog/56a93a7dd97351872e7341e0/unpublish","urlRemovePost":"/blog/56a93a7dd97351872e7341e0/removePost","urlDraft":"/blog/partner/457/draft","urlDraftTemplate":"/blog/partner/%slug%/draft","urlRemoveDraft":"/blog/56a93a7dd97351872e7341e0/removeDraft","urlTagSuggest":"/blog/api/suggest/partner","urlAfterDelete":"/blog/partner","isAuthor":false,"subscribeUrl":"/blog/api/subscribe/56a93a7dd97351872e7341e0","unsubscribeUrl":"/blog/api/unsubscribe/56a93a7dd97351872e7341e0","urlEditPostPage":"/blog/partner/56a93a7dd97351872e7341e0/edit","urlForTranslate":"/blog/post/translate","urlRelateIssue":"/blog/post/updateIssue","urlUpdateTranslate":"/blog/post/updateTranslate","urlLoadTranslate":"/blog/post/loadTranslate","urlTranslationStatus":"/blog/partner/457/translationInfo","urlRelatedArticles":"/blog/api/relatedArticles/partner/457","author":{"id":"119287251","uid":{"value":"119287251","lite":false,"hosted":false},"aliases":{},"login":"i.alex.under","display_name":{"name":"i.alex.under","avatar":{"default":"20706/119287251-18411626","empty":false}},"address":"i.alex..mds.yandex.net/get-yapic/20706/119287251-18411626/islands-middle","isYandexStaff":false},"originalModificationDate":"1970-01-01T00:00:00.000Z","socialImage":{"orig":{"fullPath":"http://avatars.yandex.net/get-yablog/4611686018427441880/normal"}}}}}">

    Похожие статьи