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

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

Выяснилось, что валидатор validator.w3.org вполне лояльно относится к синхронной версии кода счетчика и информера Яндекс.Метрики, но просто негодует, когда его заменяют асинхронными участками. Также выяснилось, что Яндекс.Метрика предоставляет пользователям (вебмастерам) возможность выбора синхронного или асинхронного вида счетчика среди прочих дополнительных настроек.

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

ОЧЕНЬ ВАЖНО : скриншоты и повествование мы будем вести с использованием НОВОЙ Метрики от Яндекс. Это будет правильно, так как с 22 июня 2015 года данная версия будет основной, тогда как старый дизайн Метрики постепенно станет уходить в прошлое.

Синхронный и асинхронный код счетчика Яндекс.Метрики: как поменять

Шаг №1

Шаг №2

Отыскиваем в списке своих сайтов ту площадку, код счетчика или информера которой нужно сменить, и запускаем функцию «Редактировать» (шестеренка с правой стороны – в новой версии Метрики).

Шаг №3

В опциях редактирования нас будет интересовать пункт меню «Код счетчика».

Шаг №4

Отыскиваем здесь функцию «асинхронный», на которой может стоять или отсутствовать галочка. Если такая галочка есть – вы видите асинхронный код счетчика в после ниже. Если же галочку снять – код счетчика Яндекс.Метрики станет стандартным – синхронным.

Шаг №5

Здесь же вы можете проверить и установить наличие или отсутствие ИНФОРМЕРА. Ранее мы говорили об этом в статье сайта « ». Так вот сейчас эти настройки выглядят в новой версии Метрики именно так и правятся именно здесь.

Вот, собственно, и все. В любой момент вы можете:

— сменить асинхронный код счетчика на синхронных;
— установить обновленный и быстрый асинхронный код;
— удалить или добавить информер счетчика Яндекс.Метрики.

Обновленный код счетчика нужно вставить на сайт:

Мы выпустили новую книгу «Контент-маркетинг в социальных сетях: Как засесть в голову подписчиков и влюбить их в свой бренд».

Подписаться

Что такое асинхронный код счетчика?

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

Асинхронным этот код называется из-за того, что выполняется параллельно всем остальным скриптам. Что это значит?

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

Данные, отправляемые этим скриптом, формируют статистические объекты. К ним относятся:

  • просмотр страницы;
  • внешний переход;
  • пользователь.

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

Где взять код Google Analytics и Яндекс.Метрики?

Чтобы получить код Google Analytics вам необходимо:

  1. Зарегистрироваться или войти в сервисы Google.
  2. Зайти во вкладку «Администраторы», заполнить поля «Название аккаунта», «Название сайта», «URL сайта». Для каждого нового сайта рекомендуется создавать новый аккаунт.

3. Принять условия использования.

  1. После этого вам станут доступны идентификаторы и код отслеживания Google Analytics.

Чтобы получить ассинхронный код в Яндекс. Метрике:

  1. Зарегистрируйтесь или авторизируйтесь на сервисе.
  2. Нажмите кнопку «Добавить счетчик».

  1. Откроется окно нового счетчика. Заполните поля, поставьте галочку «Я принимаю условия».

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

Куда ставить код 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 со строкой кода сразу после открывающего тега .

Мы особенно рекомендуем использовать асинхронный код загрузки, если ваши сайты построены на технологии 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"}}}}}">

Итак, клонируете репозиторий и переходите на ветку Part_1:

Git clone https://github.com/Peleke/promises/ git checkout Part_1-Basics

Вы на пути к истине. Наш маршрут включает в себя следующие вопросы:

  • Проблема функций обратного вызова
  • Промисы: определения и замечания из спецификации
  • Промисы и не-инверсия управления
  • Управление потоком с промисами
  • Осознаем смысл then , reject и resolve

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

Если вы достаточно времени работали с JavaScript, то вы уже слышали, что он фундаментально неблокирующий или асинхронный . Но что это означает?

Синхронный и асинхронный код

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

// readfile_sync.js "use strict"; // Этот пример из Node, поэтому не запускайте его в браузере. const filename = "text.txt", fs = require("fs"); console.log("Reading file . . . "); // readFileSync БЛОКИРУЕТ выполнение до возврата значения. // Программа будет ждать и ничего не выполнять, // пока эта операция не завершится. const file = fs.readFileSync(`${__dirname}/${filename}`); // Это ВСЕГДА будет выводится после завершения readFileSync. . . console.log("Done reading file."); // . . . А здесь ВСЕГДА будет выводится содержимое "file". console.log(`Contents: ${file.toString()}`);

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

// readfile_async.js "use strict"; // Этот пример из Node, поэтому не запускайте его в браузере. const filename = "text.txt", fs = require("fs"), getContents = function printContent (file) { try { return file.toString(); } catch (TypeError) { return file; } } console.log("Reading file . . . "); console.log("=".repeat(76)); // readFile выполняется АСИНХРОННО. // Программа продолжит выполнять то, что после LINE A // пока readFile делает свое дело. Мы скоро подробно обсудим // функции обратного вызова, пока просто обратите внимание на порядок логов let file; fs.readFile(`${__dirname}/${filename}`, function (err, contents) { file = contents; console.log(`Uh, actually, now I"m done. Contents are: ${ getContents(file) }`); }); // LINE A // Это ВСЕГДА будет выводится до завершения чтения файла. // Эти логи вводят в заблуждение и бесполезны. console.log(`Done reading file. Contents are: ${getContents(file)}`); console.log("=".repeat(76));

Основное преимущество синхронного кода состоит в том, что его проще читать и воспринимать: синхронные программы выполняются сверху вниз и строка n всегда завершается перед строкой n+1.

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

И именно поэтому JavaScript сделан неблокирующим в своей основе.

Вызов асинхронности

Переход к асинхронности дает нам скорость и забирает у нас линейность. Даже простой скрипт из примера выше демонстрирует это. Запомните:

  1. Нет способа узнать, когда file будет доступен, кроме как передав управление readFile , который сообщит нам, когда все будет готово;
  2. Наша программа больше не выполняется в том же порядке, в котором читаются, что усложняет их понимание.

Этих проблем достаточно, чтобы занять нас до конца статьи.

Колбэки и фолбэки

Попробуем немного упростить наш пример с readFile .

"use strict"; const filename = "throwaway.txt", fs = require("fs"); let file, useless; useless = fs.readFile(`${__dirname}/${filename}`, function callback (error, contents) { file = contents; console.log(`Got it. Contents are: ${contents}`); console.log(`. . . But useless is still ${useless}.`); }); // Thanks to Rava for catching an error in this line. console.log(`File is ${useless}, but that"ll change soon.`);

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

Вопрос в том, как мы можем узнать, что чтение завершено?

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

Работает это примерно так: readFile смотрит,что находится внутри ${__dirname}/${filename} , а программа занимается своими делами. Как только readFile узнает, что там, он выполняет callback с contents в качестве аргумента, а в случае ошибки возвращает error .

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

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

Функции обратного вызова это работающее решение, но не идеальное. У них есть две большие проблемы:

  1. Инверсия управления
  2. Сложная обработка ошибок

Инверсия контроля

Первая проблема это проблема доверия.

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

На практике это, конечно, не столь фатально: мы пишем функции обратного вызова почти 20 лет и до сих пор не поломали интернет. И в данном случае, мы знаем, что достаточно безопасно полагаться на код ядра Node.

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

Неявная обработка ошибок

В синхронном коде вы можете использовать try / catch / finally для обработки ошибок.

"use strict"; // Этот пример из Node, поэтому не запускайте его в браузере. const filename = "text.txt", fs = require("fs"); console.log("Reading file . . . "); let file; try { // Неправильное имя файла. D"oh! file = fs.readFileSync(`${__dirname}/${filename + "a"}`); console.log(`Got it. Contents are: "${file}"`); } catch (err) { console.log(`There was a/n ${err}: file is ${file}`); } console.log("Catching errors, like a bo$$.");

Асинхронный код пытается, конечно, но…

"use strict"; // Этот пример из Node, поэтому не запускайте его в браузере. const filename = "throwaway.txt", fs = require("fs"); console.log("Reading file . . . "); let file; try { // Неправильное имя файла. D"oh! fs.readFile(`${__dirname}/${filename + "a"}`, function (err, contents) { file = contents; }); // Это не будет выполняться пока file равен undefined console.log(`Got it. Contents are: "${file}"`); } catch (err) { // В этом случае, catch должен запускаться, но это никогда не произойдет. // Это потому, что readFile передает ошибки в коллбэк, // а не возвращает. console.log(`There was a/n ${err}: file is ${file}`); }

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

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

"use strict"; // Этот пример из Node, поэтому не запускайте его в браузере. const filename = "throwaway.txt", fs = require("fs"); console.log("Reading file . . . "); fs.readFile(`${__dirname}/${filename + "a"}`, function (err, contents) { if (err) { // catch console.log(`There was a/n ${err}.`); } else { // try console.log(`Got it. File contents are: "${file}"`); } });

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

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

Промисы

Представьте, вы только что заказали весь каталог You Don’t Know JS от O’Reilly. За ваши с трудом заработанные деньги они прислали расписку, что в следующий понедельник вы получите новенькую стопку книг. До этого счастливого понедельника никаких книг у вас не будет - но вы верите, что они появятся, так как вам пообещали (promise) прислать их.

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

Конечно, через несколько дней O’Reilly может сообщить о том, что с понедельником не судьба и книги будут чуток позже, другими словами, нужное значение будет в будущем. Вы относитесь к промису, как к ожидаемому значению и пишете код так, как будто оно уже у вас есть.

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

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

Жизненный цикл промиса: краткий обзор состояний

Представьте, что вы используете промис для вызова API.

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

Как только сервер ответил, у нас есть два возможных исхода:

  1. Промис получает ожидаемое значение, значит, он выполнен (fulfilled) . Ваши книжки пришли.
  2. Где-то по ходу выполнения произошла ошибка, промис отклонен (rejected) . Вы получили уведомление о том, что никаких книжек не будет.

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

Теперь, когда мы разобрались с основными понятиями, посмотрим, как это все использовать.

Фундаментальные методы промисов

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

В этом разделе мы ближе рассмотрим базовое использование промисов:

  1. Создание промисов с конструктором;
  2. Обработка успешного результата с resolve ;
  3. Обработка ошибок с reject ;
  4. Настройка управления потоком с then и catch .

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

Создание промисов

Самый простой способ это создание промисов непосредственно с помощью конструктора.

"use strict"; const fs = require("fs"); const text = new Promise(function (resolve, reject) { // Does nothing })

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

  1. Аргумент resolve это функция, инкапсулирующая то, что мы хотим сделать при получении ожидаемого значения . Когда мы получаем ожидаемое значение (val), мы передаем его resolve в качестве аргумента: resolve(val) .
  2. Аргумент reject это тоже функция, представляющая наши действия в случае получения ошибки. Если мы получим ошибку (err), мы вызовем reject с ней: reject(err) .
  3. Наконец, функция, переданная нами в конструктор промиса, обрабатывает сам асинхронный код. Если она возвращает ожидаемый результат, мы вызываем resolve с полученным значением. Если она выбрасывает ошибку, мы вызываем reject с этой ошибкой.

В нашем примере мы обернем fs.readFile в промис. Как должны выглядеть наши resolve и reject ?

  1. При успехе мы хотим вызвать console.log для вывода содержимого файла.
  2. При неудаче мы поступим аналогично: выведем ошибку в консоль.

Таким образом мы получим следующее:

// constructor.js const resolve = console.log, reject = console.log;

Затем нам надо написать функцию, которую мы передаем конструктору. Запомните, нам нужно сделать следующее:

  1. Прочитать файл;
  2. В случае успеха выполнить resolve с его содержимым;
  3. При неудаче выполнить reject с полученной ошибкой.

Таким образом:

// constructor.js const text = new Promise(function (resolve, reject) { // Normal fs.readFile call, but inside Promise constructor . . . fs.readFile("text.txt", function (err, text) { // . . . Call reject if there"s an error . . . if (err) reject(err); // . . . And call resolve otherwise. else // fs.readFile возвращает buffer, поэтому надо применить метод toString(). resolve(text.toString()); }) })

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

Она дала обещание, а затем…

Проблема в том, что мы написали наши методы resolve и reject , но на самом деле не передали их в промис. Для того, чтобы сделать это, нам надо ознакомиться с еще одной базовой функцией для управления потоком на основе промисов: then (затем).

Каждый промис обладает методом then , принимающим две функции в качестве аргументов: resolve и reject , именно в таком порядке. Вызов then в промисе и передача ему этих двух функций, делает их доступными для конструктора промиса.

// constructor.js const text = new Promise(function (resolve, reject) { fs.readFile("text.txt", function (err, text) { if (err) reject(err); else resolve(text.toString()); }) }) .then(resolve, reject);

Так промис прочитает файл и вызовет написанный нами метод resolve в случае успеха.

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

Синтаксический сахар для обработки ошибок

Мы передали then две функции: resolve , для вызова в случае успеха и reject на случай ошибки.

У промисов также есть функция похожая на then , называемая catch . Она принимает обработчик reject в качестве единственного аргумента.

Так как then всегда возвращает промис, в нашем примере мы можем только передать then обработчик resolve , а после этого подключить в цепочку catch с обработчиком reject .

Const text = new Promise(function (resolve, reject) { fs.readFile("tex.txt", function (err, text) { if (err) reject(err); else resolve(text.toString()); }) }) .then(resolve) .catch(reject);

Наконец, стоит упомянуть, что catch(reject) это всего лишь синтаксический сахар для then(undefined, reject) . То есть мы можем также написать:

Const text = new Promise(function (resolve, reject) { fs.readFile("tex.txt", function (err, text) { if (err) reject(err); else resolve(text.toString()); }) }) .then(resolve) .then(undefined, reject);

Но такой код будет менее читаемым.

Заключение

Промисы это незаменимый инструмент для асинхронного программирования. Они могут напугать поначалу, но только пока вы с ними незнакомы: используйте их пару раз и они станут такими же естественными для вас как if / else .

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

В качестве дополнительной литературы ознакомьтесь со статьей Доменика Дениколы States and Fates , чтобы овладеть терминологией и с главой Кайла Симпсона о промисах из той стопки книг, на примере которой мы разбирали промисы.

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

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

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

Параллельность 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 .



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: