JavaScript: методы асинхронного программирования. Основы построения асинхронных приложений
Объявлений переменных и функций, но чтобы превратить эту особенность 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 . Здесь, так же как и при загрузке списка пользователей, можно обработать ошибки или полезные данные, в которых содержится список репозиториев пользователя.
Если придать нашему коду более завершённый вид, снабдить его средствами обработки ошибок и отделить определение функций обратного вызова от кода выполнения запроса, что улучшит читабельность программы, получится следующее:
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 , в данном случае - это всего одна операция.
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
- асинхронный код
К написанию данной пошаговой инструкции, как всегда, привели реальные практические сложности, которые были связаны с валидностью программного кода сайта. При попытке подправить код в соответствии с требованиями заявленного Доктайпа, оказалось, что ошибки возникают непосредственно в асинхронных участках счетчика Яндекс.Метрики. Соответственно, начался поиск решений этой проблемы, и конечный результат был достигнут.
Выяснилось, что валидатор validator.w3.org вполне лояльно относится к синхронной версии кода счетчика и информера Яндекс.Метрики, но просто негодует, когда его заменяют асинхронными участками. Также выяснилось, что Яндекс.Метрика предоставляет пользователям (вебмастерам) возможность выбора синхронного или асинхронного вида счетчика среди прочих дополнительных настроек.
Так вот о том, как сменить синхронный код Яндекс.Метрики асинхронным вариантом и наоборот, мы и хотели бы поведать нашему читателю в рамках данной небольшой пошаговой инструкции. Всем тем читателям, которые плохо понимают, о чем вообще идет речь, мы рекомендовали просто пропустить данную инструкцию и перейти к прочтению более важных и интересных статей. А эта публикация будет носить сугубо узкую и ограниченную спецификацию.
ОЧЕНЬ ВАЖНО : скриншоты и повествование мы будем вести с использованием НОВОЙ Метрики от Яндекс. Это будет правильно, так как с 22 июня 2015 года данная версия будет основной, тогда как старый дизайн Метрики постепенно станет уходить в прошлое.
Синхронный и асинхронный код счетчика Яндекс.Метрики: как поменять
Шаг №1
Шаг №2
Отыскиваем в списке своих сайтов ту площадку, код счетчика или информера которой нужно сменить, и запускаем функцию «Редактировать» (шестеренка с правой стороны – в новой версии Метрики).
Шаг №3
В опциях редактирования нас будет интересовать пункт меню «Код счетчика».
Шаг №4
Отыскиваем здесь функцию «асинхронный», на которой может стоять или отсутствовать галочка. Если такая галочка есть – вы видите асинхронный код счетчика в после ниже. Если же галочку снять – код счетчика Яндекс.Метрики станет стандартным – синхронным.
Шаг №5
Здесь же вы можете проверить и установить наличие или отсутствие ИНФОРМЕРА. Ранее мы говорили об этом в статье сайта « ». Так вот сейчас эти настройки выглядят в новой версии Метрики именно так и правятся именно здесь.
Вот, собственно, и все. В любой момент вы можете:
— сменить асинхронный код счетчика на синхронных;
— установить обновленный и быстрый асинхронный код;
— удалить или добавить информер счетчика Яндекс.Метрики.
Обновленный код счетчика нужно вставить на сайт:
Мы выпустили новую книгу «Контент-маркетинг в социальных сетях: Как засесть в голову подписчиков и влюбить их в свой бренд».
Что такое асинхронный код счетчика?
Асинхронный код отслеживания (счетчика) - это код скрипта, вставляемого в тело сайта, разработанный для отслеживания статистики посещаемости. К ним относятся скрипты Google Analytics и Яндекс.Метрики.
Асинхронным этот код называется из-за того, что выполняется параллельно всем остальным скриптам. Что это значит?
По умолчанию, все JavaScript выполняются последовательно, и, если в первом коде есть ошибка или элемент, задерживающий загрузку остальных скриптов, то в отслеживание статистики посещений может закрасться ошибка. Асинхронный код выполняется параллельно остальным процессам и самым первым при загрузке страницы. Это обеспечивает его 100% срабатывание и точный сбор данных.
Данные, отправляемые этим скриптом, формируют статистические объекты. К ним относятся:
- просмотр страницы;
- внешний переход;
- пользователь.
Эти данные нужны для того, чтобы сформировать общую картину о сайте: количество посетителей, их пол и возраст, географию и время, проведенное на сайте, страницы входа и выхода, и т. д.
Где взять код Google Analytics и Яндекс.Метрики?
Чтобы получить код Google Analytics вам необходимо:
- Зарегистрироваться или войти в сервисы Google.
- Зайти во вкладку «Администраторы», заполнить поля «Название аккаунта», «Название сайта», «URL сайта». Для каждого нового сайта рекомендуется создавать новый аккаунт.
3. Принять условия использования.
- После этого вам станут доступны идентификаторы и код отслеживания Google Analytics.
Чтобы получить ассинхронный код в Яндекс. Метрике:
- Зарегистрируйтесь или авторизируйтесь на сервисе.
- Нажмите кнопку «Добавить счетчик».
- Откроется окно нового счетчика. Заполните поля, поставьте галочку «Я принимаю условия».
- Вашему счетчику сайту будет присвоен номер. В разделе «Код счётчика» вы найдете код, который сможете вставить в сайт.
Куда ставить код Google Analytics и Яндекс.Метрики?
Новый код отслеживания ставится в самое начало страницы - в блок
. В основном, это делается через редактор или FTP.Но, если вы, например, владелец сайта на WordPress, то для вас доступны специальные плагины с кодом «Google Analytics for WordPress» или Google Analyticator. Вы просто вставляете ваш Tracking ID в поле Analytics Profile, и система выполняет авторизацию самостоятельно.
Владельцы CMS OpenCart могут вставить код Google Analytics, выбрав вкладки Extensions -> Analytics и вставив код счётчика в соответствующее поле Google Analytics.
Также владельцы динамических.php-сайтов могут самостоятельно вставить код в файл header. php, но это грозит тем, что код пропадет при обновлении системы, и его придётся размещать заново. Либо создать файл analyticstracking. php и добавить его во все шаблоны страниц PHP со строкой кода сразу после открывающего тега
.Коды (асинхронный и синхронный): формирование и установка
Достаточно часто у наших клиентов возникают вопросы относительно того, как будет влиять наличие встроенного счетчика на работу сайта. Скрипты могут увеличивать время загрузки страницы.
Ранее в Google Analytics предлагался стандартный код, который влиял на скорость загрузки страницы и на сбор данных.
На текущий момент Google Analytics предлагает асинхронную версию кода, которая обладает рядом преимуществ относительно стандартного кода.
Асинхронный код
– это улучшенный фрагмент кода JavaScript, который загружает код отслеживания ga.js в фоновом режиме, без приостановки загрузки других скриптов и контента на страницах вашего веб-сайта.
Преимущества использования асинхронного кода:
Если у вас установлена стандартная версия кода, рекомендуется обновить код на асинхронный. Для этого необходимо удалить старый фрагмент кода, и на его место встроить асинхронный код. После установки асинхронного кода данные начнут появляться в аккаунте в течение 24 часов.
- не использовать 2 кода одновременно, это может привести к неточности данных;
- асинхронный код располагается внизу раздела , в отличие от стандартного, который располагался непосредственно перед закрывающим тегом .
Формирование и установка кода
1. Код создается на уровне профиля. Необходимо перейти на вкладку «Администратор».
3. Полученный код необходимо скопировать и встроить на страницу перед закрывающим тегом .
*Одним из главных преимуществ асинхронного кода является то, что его можно добавить в верхней части HTML-кода. В результате повышается вероятность передачи информации о посещении до того, как пользователь покинет страницу.
В данной статье мы не рассматривали модификации кода, для этого будет написан отдельный пост.
Сегодня каждый день появляются новые языки программирования - Go, Rust, CoffeeScript - все, что угодно. Я решил, что я тоже горазд придумать свой язык программирования, что миру не хватает какого-то нового языка…
Дамы и господа, я представляю вам сегодня Schlecht!Script - чумовой язык программирования. Мы все должны начать им пользоваться прямо сейчас. В нем есть все то, к чему мы привыкли - в нем есть условные операторы, есть циклы, есть функции и функции высших порядков. В общем, в нем есть все, что нужно нормальному языку программирования.
Что в нем не очень обычно, что может даже оттолкнуть, на первый взгляд, - это то, что в Schlecht!Script функции имеют цвет.
Т.е., когда вы объявляете функцию, когда вы ее вызываете, вы явным образом указываете ее цвет.
Функции бывают красные и синие - двух цветов.
Важный момент: внутри синих функций вы можете вызывать только другие синие функции. Вы не можете вызывать красные функции внутри синих функций.
Внутри красных функций вы можете вызывать и красные, и синие функции.
Я решил, что должно быть так. В каждом языке должно быть так.
Тонкий момент: красные функции писать и вызывать больно! Что я имею в виду, когда говорю «больно»? Дело в том, что сейчас я изучаю немецкий язык, и я решил, что красные функции мы должны все называть только на немецком языке, иначе интерпретатор просто не поймет, что вы ему пытаетесь впихнуть, и он просто не будет это выполнять.
Вот так вы должны писать функции на немецком языке:
«!» обязателен - мы же на немецком пишем, в конце концов.
Как писать на таком языке? У нас есть два способа. Мы можем использовать только синие функции, в которые писать не больно, но внутри мы не можем пользоваться красными функциями. Этот подход не будет работать, потому что в порыве вдохновения я написал половину стандартной библиотеки на красных функциях, так что, простите…
Вопрос к вам - стали бы вы использовать такой язык? Продал ли я вам Schlecht!Script?
Ну, у вас, как бы, нет выбора. Простите…
JavaScript - отличный язык, мы все его любим, мы все здесь собрались, потому что мы любим JavaScript. Но проблема в том, что JavaScript наследует некоторые черты Schlecht!Script, и я, конечно, не хочу хвастаться, но, по-моему, они украли пару моих идей.
Что именно они наследуют? в JavaScript есть красные и синие функции. Красные функции в JavaScript - это асинхронные функции, синие - синхронные функции. И все прослеживается, все та же цепочка… Красные функции вызывать больно в Schlecht!Script, а асинхронные функции вызывать больно в JavaScript.
И внутри синих функций мы не можем писать красные функции. Я еще скажу об этом позже.
Почему это больно? Откуда боль при вызове и при написании асинхронных функций?
У нас по-другому работают условные операторы, циклы, return. У нас не работает try/catch, и асинхронные функции ломают абстракцию.
О каждом пункте немного подробнее.
Вот так выглядит синхронный код, где shouldProcess и process - это функции синхронные, и работают условные операторы, работает for, в общем, все хорошо.
То же самое, но асинхронное, будет выглядеть вот так вот:
Там появилась рекурсия, мы передаем состояние в параметры, в функцию. В общем, смотреть прямо-таки неприятно. У нас не работает try/catch и, я думаю, мы все знаем, что если мы обернем синхронный блок кода в try/catch, исключения мы не поймаем. Нам нужно будет передать callback, перенавесить обработчик событий, в общем, у нас нет try/catch…
И асинхронные функции ломают абстракцию. Что я имею в виду? Представьте, что вы написали кэш. Вы сделали кэш пользователей в памяти. И у вас есть функция, которая читает из этого кэша, которая, естественно, синхронная, потому что все в памяти. Завтра к вам приходят тысячи, миллионы, миллиарды пользователей, и вам нужно положить этот кэш в Redis. Вы кладете кэш в Redis, функция становится асинхронной, потому что из-за Redis"а мы можем читать только асинхронно. И, соответственно, весь стек, который вызывал вашу синхронную функцию, придется переписать, потому что теперь весь стек становится асинхронным. Если какая-то функция зависела от функции чтения из кэша, она так же будет теперь асинхронной.
В общем и целом, говоря об асинхронности в JavaScript, можно сказать, что все там грустно.
Но что мы все об асинхронности? Давайте немного обо мне поговорим, наконец.
Я пришел, чтобы вас всех спасти. Ну, я попробую это сделать.
Меня зовут Андрей, я работаю в стартапе «Productive Mobile» в Берлине. Я помогаю с организацией MoscowJS и я являюсь соведущим RadioJS. Мне очень интересна тема асинхронности и не только в JavaScript, я считаю, что, в принципе, это определяющий момент языка. То, как язык работает с асинхронностью, определяет его успех и то, насколько людям приятно и удобно с ним работать.
Говоря об асинхронности конкретно в JavaScript, мне кажется, у нас есть два сценария, с которыми мы постоянно взаимодействуем. Это обработка множества событий и обработка единичных асинхронных операций.
Множество событий - это, например, событие DOM или подключение к серверу - что-то, что излучает множество нескольких типов событий.
Единичная операция - это, к примеру, чтение из базы. Единичная асинхронная операция возвращает нам либо один результат, либо возвращает ошибку. Больше никаких вариантов нет.
И, говоря об этих двух сценариях, интересно порассуждать: вот, типа, асинхронность - плохо, в общем, все грустно… А что мы на самом деле хотим? Как бы выглядел идеальный асинхронный код?
А хотим мы, мне кажется, контроля потока управления. Мы хотим, чтобы наши условные операторы, циклы работали в синхронном коде так же, как в асинхронном.
Мы хотим обработки исключений. Зачем нам try/catch, если мы не можем его использовать в асинхронных операциях? Это просто странно.
И желательно, конечно, иметь единый интерфейс. Почему асинхронная функция должна писаться и вызываться по-другому, по сравнению с синхронной? Этого не должно быть.
Вот, чего мы хотим.
А что у нас есть сегодня, и какие инструменты у нас появятся в будущем?
Если мы говорим об ECMAScript 6 (это, в принципе, то, о чем я буду говорить сегодня), для работы с множеством событий у нас есть EventEmitter и Stream, а для работы с единичными асинхронными операциями - Continuation Passing Style (они же callback"и), Promises и Coroutines.
В ECMAScript 7 у нас появятся Async Generators для работы с множеством событий и Async/Await - для работы с единичными асинхронными операциями.
Об этом и поговорим.
Начнем с того, что у нас есть в ECMAScript 6 для работы с множеством асинхронных событий. Напомню, например, это обработка событий мыши или нажатий на клавиши. У нас есть паттерн EventEmitter, который реализован в браузере в Node.js. Он встречается практически в любой API, где мы работаем с множеством событий. EventEmitter говорит нам, что мы можем создать объект, который излучает события, и навешивать обработчики на каждый тип события.
Интерфейс очень простой. Мы можем добавлять EventListener, убирать EventListener по названию event"а, передавая туда сallback.
К примеру, в XMLHttpRequest, когда я говорю о множестве событий, я имею в виду, что у нас может быть множество событий progress. Т.е. по мере того, как мы загружаем какие-то данные с помощью AJAX-запроса, нам выстреливают события progress, и по одному разу выстреливают события load, abort и error:
Error - это особенное событие, универсальное событие в EventEmitter"ах и в Stream"ах для того чтобы уведомить пользователя об ошибке.
Есть множество реализаций:
Здесь перечислены только несколько, и в конце доклада будет ссылка, где все эти реализации есть.
Важно сказать, что в Node.js EventEmitter встроен по умолчанию.
Итак, это то, что у нас есть практически по стандарту в API и браузерах в Node.js.
Что у нас еще есть для работы с множеством событий? Stream.
Stream - это поток данных. Что такое данные? Это могут быть бинарные данные, например, данные из файла, текстовые данные, либо объекты или события. Самые популярные примеры:
Есть несколько типов потоков:
Здесь мы рассматриваем цепочку преобразований из Stylus файлов в css файлы, добавляя автопрефиксер, потому что все мы любим Андрея Ситника и его автопрефиксер.
Вы видите, что у нас есть несколько типов потоков - поток-источник это gulp.src, который читает файлы и излучает объекты файлы, которые потом идут в потоки преобразования. Первый поток преобразования делает из stylus файла css, второй поток преобразования добавляет префиксы. И последний тип потоков - это поток-потребитель, который принимает эти объекты, пишет что-то куда-то на диск, и ничего не излучает.
Т.е. у нас есть 3 типа потоков - источник данных, преобразование и потребитель. И эти паттерны прослеживаются везде, не только в gulp, но и при наблюдении за DOM событиями. У нас есть потоки, которые излучают DOM-события, которые их преобразуют и что-то, что потребляя эти DOM-события, возвращает конкретный результат.
Это то, что можно назвать конвейером. С помощью потоков мы можем выстраивать такие цепочки, когда объект кладется куда-то в начало конвейера, проходит цепочку преобразований и, когда к нему подходят люди, что-то там меняют, добавляют, удаляют, и в конечном итоге у нас выходит какой-нибудь автомобиль.
Есть несколько реализаций потоков, или они же Observables:
В Node.js потоки встроенные - это Node Streams.
Итак, у нас есть EventEmitter и Stream. EventEmitter по умолчанию есть во всех API, а. Stream - это надстройка, которую мы можем использовать для того, чтобы унифицировать интерфейс обработки множества событий.
Когда мы говорим о тех критериях, по которым мы сравниваем асинхронные API, у нас не работают, по большому счету, операторы return, операторы-циклы, у нас не работают try/catch, естественно, и до единого интерфейса с синхронными операциями нам еще далеко.
В общем, для работы с множеством событий в ECMAScript 6 все не очень хорошо.
Когда мы говорим об единичных асинхронных операциях, у нас есть 3 подхода в ECMAScript 6:
Continuation Passing Style, они же - callback"и.
Я думаю, вы все к этому уже привыкли. Это когда мы делаем асинхронный запрос, передаем туда callback, и callback будет вызван либо с ошибкой, либо с результатом. Это распространенный подход, он есть и в браузере в Node.
Проблемы с этим подходом, я думаю, вы тоже все понимаете.
Вот так бы мы получали ленту твитов пользователей асинхронно, если бы все функции были синхронными.
Тот же самый код, но синхронно, выглядит следующим образом:
Можно увеличить шрифт, чтобы было виднее. И еще немного увеличить… И мы прекрасно понимаем, что это Schlecht!Script.
Я говорил, что они украли мою идею.
Continuation Passing Style - это стандартный API в браузере в Node.js, мы работаем с ними постоянно, но это неудобно. Поэтому у нас есть Promises. Это объект, который представляет собой асинхронную операцию.
Promise - это как бы обещание что-то сделать в будущем, асинхронно.
Мы можем навешивать callback"и на асинхронную операцию с помощью метода then, и это, в принципе, основной метод в API. И мы можем, очень важно, чейнить Promises, мы можем вызывать then последовательно, и каждая функция, которая передается в then, также может возвращать Promises. Вот так выглядит запрос ленты пользователя из твиттера на Promises.
Если мы сравниваем этот подход с Continuation Passing Style, Promises, естественно, удобнее пользоваться - они дают нам возможность писать гораздо меньше бойлерплейта.
Continuation Passing Style, по-прежнему, используется во всех API по умолчанию, в Node.js, в io.js., и они даже не планируют переходы на Promises по нескольким причинам. Сначала многие говорили, что причины - это производительность. И это действительно так, исследования 2013 года показывают, что Promises сильно позади callback"ов. Но с появлением таких библиотек как bluebird, мы уже можем уверенно сказать, что это не так, потому что Promises в bluebird приближаются в производительности к callback"ам. Важный момент: почему Promises не рекомендуют использовать в API до сих пор? Потому что, когда вы выдаете Promises из вашей API, вы навязываете имплементацию.
Все Promises библиотеки должны подчиняться стандарту, но выдавая Promises, вы так же выдаете и имплементацию, т.е. если вы написали свой код, используя медленные Promises, и выдаете медленный Promises из API, это будет не очень приятно пользователям. Поэтому для внешних API, конечно же, по-прежнему рекомендуют использовать callback"и.
Реализаций Promises - масса, и если вы не написали свою реализацию, вы ненастоящий JavaScript-программист. Я не написал свою реализацию Promises, поэтому мне пришлось придумать свой язык.
Итак, Promises, в общем, чуть меньше бойлерплейта, но, тем не менее, все еще не так хорошо.
Что по поводу Coroutines? Здесь уже начинаются интересные штуки. Представьте…
Это интересно. Мы были на JSConf в Будапеште, и там был сумасшедший человек, который на JavaScript программировал квадракоптер и что-то еще, и половину из того, что он пытался нам показать, у него не получалось. Поэтому он постоянно говорил: «OK, теперь представьте… Этот квадракоптер взлетел и все получилось...».
Представьте, что мы можем поставить функцию на паузу в какой-то момент ее выполнения.
Здесь функция получает имя пользователя, она лезет в базу, получает объект пользователя, возвращает его имя. Естественно, «залезть в базу» - функция getUser асинхронная. Что, если мы могли бы поставить функцию getUserName на паузу в момент вызова getUser? Вот, мы выполняем нашу getUserName функцию, дошли до getUser и остановились. getUser сходил в базу, получил объект, вернул его в функцию, мы продолжаем выполнение. Насколько круто это было бы.
Дело в том, что Coroutines дают нам эту возможность. Coroutines - это функция, которую мы можем приостанавливать и возобновлять в любой момент времени. Важный момент: мы не останавливаем всю программу.
Это не блокирующая операция. Мы останавливаем выполнение конкретной функции в конкретном месте.
Как getUserName выглядит с помощью генераторов на JavaScript? Нам нужно добавить «*» в объявление функции, чтобы сказать о том, что функция возвращает генератор. Мы можем использовать ключевое слово «yield» в том месте, где мы хотим поставить функцию на паузу. И важно помнить, что getUser здесь возвращает Promises.
Т.к. генераторы изначально придумали для того чтобы делать ленивые последовательности в JavaScript, по большому счету, использовать их для синхронного кода - это хак. Поэтому нам нужны библиотеки, чтобы как-то это компенсировать.
Здесь мы используем «co» для того, чтобы обернуть генератор и вернуть нам асинхронную функцию.
Итого, вот, что у нас получается:
У нас функция, внутри которой мы можем использовать if, for и другие операторы.
Чтобы вернуть значение, мы просто пишем return, так же, как в синхронной функции. Мы можем использовать try/catch внутри, и мы поймаем исключение.
Если Promises с getUser разрешится с ошибкой, это выкинется как исключение.
getUserName функция возвращает Promises, поэтому можем работать с ним так же, как с любым Promises, можем навешивать callback"и с помощью then, чейнить и т.д.
Но, как я уже сказал, использовать генераторы для асинхронного кода - это хак. Поэтому экспоузить в качестве внешней API нежелательно. Но использовать внутри приложения нормально, так что пользуйтесь, если у вас есть возможность транспайлить ваш код.
Есть множество реализаций. Какие-то используют генераторы, которые уже часть стандарта, есть Fibers, которые в Node.js работают и которые не используют генератор, а свои заморочки у них.
В общем, это третий подход для работы с единичными асинхронными операциями, и это пока еще хак, но мы уже можем использовать код, который приближен к синхронному. Мы можем использовать условные операторы, циклы и блоки try/catch.
Т.е. ECMAScript 6 для работы с единичными асинхронными операциями как бы немного приближает нас к желаемому результату, но по-прежнему проблема единого интерфейса не решена, даже в Coroutines, потому что нам нужно писать «*» специальную и использовать ключевой оператор «yield».
Итак, в ECMAScript 6 для работы с множеством событий у нас есть EventEmitter и Stream, для работы с единичными асинхронными операциями - CPS, Promises, Coroutines. И все это, вроде бы, здорово, но чего-то не хватает. Хочется большего, чего-то отважного, смелого, нового, хочется революции.
И ребята, которые пишут ES7, решили дать нам революцию и принесли для нас Async/Await и Async Generators.
Async/Await - это стандарт, который позволяет нам работать с единичными асинхронными операциями, такими, как, например, запросы в БД.
Вот так мы писали getUserName на генераторах:
А вот так тот же код выглядит с помощью Async/Await:
Все очень похоже, по большому счету, это шаг в сторону от хака к стандарту. Здесь у нас появилось ключевое слово «async», которое говорит о том, что функция асинхронная, и она вернет Promise. Внутри асинхронной функции мы можем использовать ключевое слово «await», там, где мы возвращаем Promise. И мы можем дожидаться выполнения этого Promise, мы можем ставить функцию на паузу и дожидаться выполнения этого Promise.
И так же у нас работают условные операторы, циклы, и try/catch, то бишь, асинхронные функции легализованы в ES7. Теперь мы явно говорим, что если функция асинхронная, то добавьте ключевое слово «async». И это, в принципе, не так плохо, но опять же единого интерфейса у нас нет.
Что по поводу множества событий? Здесь у нас есть стандарт, который называется Async Generators.
Что такое, вообще, множество? Как мы работаем с множеством в JavaScript?
C множеством мы работаем при помощи циклов, так давайте работать с множеством событий при помощи циклов.
Внутри асинхронной функции мы можем использовать ключевую конструкцию «for… on», которая нам позволяет итерировать по асинхронным коллекциям. Как бы.
В данном примере observe возвращает нам что-то, по чему мы можем итерировать, т.е. каждый раз, когда пользователь будет двигать мышкой, у нас будет появляться событие «mousemove». Мы попадаем в этот цикл, и обрабатываем как-то это событие. В данном случае рисуем черточки на экране.
Т.к. функция асинхронная, важно понимать, что она возвращает Promise. Но что, если мы хотим вернуть множество значений, если мы хотим, например, обрабатывать как-то сообщения из веб-сокета, фильтровать их? Т.е. у нас поступает множество и на выходе у нас множество. Здесь нам помогают асинхронные генераторы. Мы пишем «async function *» и говорим о том, что функция асинхронна, и мы возвращаем множество какое-то.
В данном случае мы смотрим на событие Message на веб-сокете и каждый раз, когда оно происходит, мы делаем какую-то проверку и, если проверка проходит, мы в возвращаемую коллекцию. Как бы добавляем этот Message.
При чем, все это происходит асинхронно. Сообщения не копятся, они возвращаются по мере того, как приходят. И здесь так же работают все наши условные операторы, циклы и try/catch.
Вопрос: что возвращает filterWSMessages?
Это точно не Promise, потому что это какая-то коллекция, что-то в этом роде… Но это и не массив.
Даже больше. Что возвращают эти Observe, которые генерят события?
А возвращают они т.н. объекты Observables. Это новое слово, но по большому счету, Observables - это потоки, это Streams. Т.о., круг замыкается.
Итого, у нас есть для работы с асинхронными единичными операциями Async/Await, для работы с множеством - Async Generators.
Давайте пройдемся и сделаем небольшую ретроспективу того, от чего мы ушли и к чему пришли.
Чтобы получить ленту твитов, в CPS мы писали бы вот такой код:
Много бойлерплейта, обработка ошибок, ручная практически и, в общем, не очень приятная.
С помощью Promise код выглядит таким вот образом:
Бойлерплейт поменьше, мы можем обрабатывать исключения в одном месте, что уже хорошо, но, тем не менее, есть эти then..., не работают ни try/ catch, ни условные операторы.
С помощью Async/Await получаем такую конструкцию:
И примерно то же нам дают Coroutines.
Здесь все шикарно, за исключением того, что нам нужно объявить эту функцию как «async».
Что касается множества событий, если мы говорим о DOM-событиях, вот так мы обрабатывали бы mousemove и рисовали бы по экрану с помощью EventEmitter"а:
Тот же самый код, но с помощью Stream"ов и библиотеки Kefir выглядит таким образом:
Мы создаем поток из событий mousemove на window, мы их каким-то образом фильтруем, и на каждое значение мы вызываем callback функцию. И, когда мы вызовем end у этого stream"а, мы автоматически отпишемся от событий в DOM, что немаловажно
Async Generators выглядит таким образом:
Это просто цикл, мы итерируем по коллекции асинхронных событий и производим какие-то операции над ними.
По-моему, это огромный путь.
Хотелось бы в заключение сказать пару слов о том, как, собственно, перестать отлаживать асинхронный код и начать жить.
- Определите вашу задачу, т.е. если вы работаете с множеством событий, имеет смысл посмотреть на Streams или, возможно даже, на Async Generators, если у вас есть транспайлер.
- Если вы работаете с базой, например, отправляете туда запросы или отправляете AJAX-запросы, которые могут либо зафейлиться, либо выполниться, используйте Promises.
- Обдумайте ваши ограничения. Если вы можете использовать транспайлер, имеет смысл посмотреть на Async/Await и Async Generators. Если вы пишете API, возможно, не имеет смысла экспоузить Promise в качестве внешней API и делать все на callback’ах.
- Используйте лучшие практики, помните про события error на потоках и на EventEmitter"ах.
- Помните про специальные методы вроде Promise.all и т.д.
Я знаю, всех вас интересует судьба Schlecht!Script, когда он будет выложен на GitHub и т.д., но дело в том, что из-за постоянной критики, обвинений в плагиате - говорят, что язык такой уже есть, ничего нового я не изобрел, я решил закрыть проект и посвятить себя, может быть, чему-то полезному, важному, интересному, возможно, я даже напишу свою библиотеку Promises.