для чего предназначены методы settimeout и setinterval
Асинхронность в JavaScript. setTimeout и setInterval
На этом уроке мы познакомимся с таймерами, которые предназначены для вызова кода на языке JavaScript через определённые промежутки времени.
setTimeout и setInterval
Отличаются они друг от друга лишь тем, что setTimeout выполняет вызов функции всего один раз, а setInterval – постоянно через указанный интервал времени.
Синтаксис setTimeout и setInterval :
Например, вызовем функцию myFunc один раз по прошествии 3 секунд:
Синхронный и асинхронный код
Выполнение такого кода движок JavaScript выполняет последовательно (т.е. строчку за строчкой). При этом перед тем, как выполнить какую-то строчку кода интерпретатор сначала помещает её в стек вызовов ( call stack ). Именно в нём происходит её разбор и исполнение. После этого происходит её извлечение из стека и переход к следующей строчке.
В это же время Web API регистрирует эту функцию и запускает таймер. Как только он завершается, он помещает эту функцию в очередь (callback queue). Очередь – это структура данных типа FIFO. Она хранит все функции в том порядке, в котором они были туда добавлены.
Очередь обратных вызовов (callback queue) обрабатывает цикл событий (event loop). Он смотрит на эту очередь и на стек вызовов (call stack). Если стек вызовов пуст, а очередь нет – то он берёт первую функцию из очереди и закидывает её в стек вызовов, в котором она уже выполняется. Вот таким образом происходит выполнения асинхронного кода в JavaScript.
Если функцию myFunc необходимо вызывать не один раз, а постоянно через каждые 3 секунды, то тогда вместо setTimeout следует использовать setInterval :
Пример, с передачей функции аргументов:
Пример, с использованием в setTimeout анонимной функции:
Если функция setTimeout по каким-то причинам не работает, то проверьте действительно ли вы передаёте ссылку на функцию, а неё результат:
Отмена таймаута (clearTimeout)
Метод setTimeout в результате своего вызова возвращает нам некий идентификатор таймера, который затем мы можем использовать для его отмены.
Синтаксис отмены таймаута:
Пример
Методы setInterval и clearInterval
Метод setInterval предназначен для вызова кода на языке JavaScript через указанные промежутки времени. Он в отличие от метода setTimeOut будет вызвать код до тех пор, пока Вы не остановите этот таймер.
Метод setInterval имеет два обязательных параметра:
Планирование: setTimeout и setInterval
Мы можем вызвать функцию не в данный момент, а позже, через заданный интервал времени. Это называется «планирование вызова».
Для этого существуют два метода:
Эти методы не являются частью спецификации JavaScript. Но большинство сред выполнения JS-кода имеют внутренний планировщик и предоставляют доступ к этим методам. В частности, они поддерживаются во всех браузерах и Node.js.
setTimeout
Например, данный код вызывает sayHi() спустя одну секунду:
Если первый аргумент является строкой, то JavaScript создаст из неё функцию.
Это также будет работать:
Но использование строк не рекомендуется. Вместо этого используйте функции. Например, так:
Начинающие разработчики иногда ошибаются, добавляя скобки () после функции:
Отмена через clearTimeout
Синтаксис для отмены:
В коде ниже планируем вызов функции и затем отменяем его (просто передумали). В результате ничего не происходит:
Повторюсь, что нет единой спецификации на эти методы, поэтому такое поведение является нормальным.
Для браузеров таймеры описаны в разделе таймеров стандарта HTML5.
setInterval
Метод setInterval имеет такой же синтаксис как setTimeout :
Все аргументы имеют такое же значение. Но отличие этого метода от setTimeout в том, что функция запускается не один раз, а периодически через указанный интервал времени.
Следующий пример выводит сообщение каждые 2 секунды. Через 5 секунд вывод прекращается:
Так что если вы запустите код выше и подождёте с закрытием alert несколько секунд, то следующий alert будет показан сразу, как только вы закроете предыдущий. Интервал времени между сообщениями alert будет короче, чем 2 секунды.
Рекурсивный setTimeout
Есть два способа запускать что-то регулярно.
Например, необходимо написать сервис, который отправляет запрос для получения данных на сервер каждые 5 секунд, но если сервер перегружен, то необходимо увеличить интервал запросов до 10, 20, 40 секунд… Вот псевдокод:
А если функции, которые мы планируем, ресурсоёмкие и требуют времени, то мы можем измерить время, затраченное на выполнение, и спланировать следующий вызов раньше или позже.
Сравним два фрагмента кода. Первый использует setInterval :
Второй использует рекурсивный setTimeout :
Для setInterval внутренний планировщик будет выполнять func(i) каждые 100 мс:
Реальная задержка между вызовами func с помощью setInterval меньше, чем указано в коде!
Вполне возможно, что выполнение func будет дольше, чем мы ожидали, и займёт более 100 мс.
В данном случае движок ждёт окончания выполнения func и затем проверяет планировщик и, если время истекло, немедленно запускает его снова.
Ниже представлено изображение, показывающее процесс работы рекурсивного setTimeout :
Рекурсивный setTimeout гарантирует фиксированную задержку (здесь 100 мс).
Это потому, что новый вызов планируется в конце предыдущего.
Есть и побочный эффект. Функция ссылается на внешнее лексическое окружение, поэтому пока она существует, внешние переменные существуют тоже. Они могут занимать больше памяти, чем сама функция. Поэтому, если регулярный вызов функции больше не нужен, то лучше отменить его, даже если функция очень маленькая.
setTimeout с нулевой задержкой
Это планирует вызов func настолько быстро, насколько это возможно. Но планировщик будет вызывать функцию только после завершения выполнения текущего кода.
Так вызов функции будет запланирован сразу после выполнения текущего кода.
Например, этот код выводит «Привет» и затем сразу «Мир»:
Первая строка помещает вызов в «календарь» через 0 мс. Но планировщик проверит «календарь» только после того, как текущий код завершится. Поэтому «Привет» выводится первым, а «Мир» – после него.
Есть и более продвинутые случаи использования нулевой задержки в браузерах, которые мы рассмотрим в главе Событийный цикл: микрозадачи и макрозадачи.
В браузере есть ограничение на то, как часто внутренние счётчики могут выполняться. В стандарте HTML5 говорится: «после пяти вложенных таймеров интервал должен составлять не менее четырёх миллисекунд.».
Аналогичное происходит при использовании setInterval вместо setTimeout : setInterval(f) запускает f несколько раз с нулевой задержкой, а затем с задержкой 4+ мс.
Это ограничение существует давно, многие скрипты полагаются на него, поэтому оно сохраняется по историческим причинам.
Этого ограничения нет в серверном JavaScript. Там есть и другие способы планирования асинхронных задач. Например, setImmediate для Node.js. Так что это ограничение относится только к браузерам.
Итого
Обратим внимание, что все методы планирования не гарантируют точную задержку.
Например, таймер в браузере может замедляться по многим причинам:
Всё это может увеличивать минимальный интервал срабатывания таймера (и минимальную задержку) до 300 или даже 1000 мс в зависимости от браузера и настроек производительности ОС.
Задачи
Вывод каждую секунду
Сделайте два варианта решения.
setTimeout и setInterval
Почти все реализации JavaScript имеют внутренний таймер-планировщик, который позволяет задавать вызов функции через заданный период времени.
В частности, эта возможность поддерживается в браузерах и в сервере Node.JS.
setTimeout
Например, следующий код вызовет alert(‘Привет’) через одну секунду:
Если первый аргумент является строкой, то интерпретатор создаёт анонимную функцию из этой строки.
То есть такая запись работает точно так же:
Использование строк не рекомендуется, так как они могут вызвать проблемы при минимизации кода, и, вообще, сама возможность использовать строку сохраняется лишь для совместимости.
Вместо них используйте анонимные функции:
Параметры для функции и контекст
Во всех современных браузерах, с учетом IE10, setTimeout позволяет указать параметры функции.
Пример ниже выведет «Привет, я Вася» везде, кроме IE9-:
…Однако, в большинстве случаев нам нужна поддержка старого IE, а он не позволяет указывать аргументы. Поэтому, для того, чтобы их передать, оборачивают вызов в анонимную функцию:
В частности, вызов метода объекта через setTimeout сработает в глобальном контексте. Это может привести к некорректным результатам.
Например, вызовем user.sayHi() через одну секунду:
Иначе говоря, эти два вызова setTimeout делают одно и то же:
К счастью, эта проблема также легко решается созданием промежуточной функции:
Функция-обёртка используется, чтобы кросс-браузерно передать аргументы и сохранить контекст выполнения.
Отмена исполнения
В следующем примере мы ставим таймаут, а затем удаляем (передумали). В результате ничего не происходит.
setInterval
Следующий пример при запуске станет выводить сообщение каждые две секунды, пока вы не нажмете на кнопку «Стоп»:
Очередь и наложение вызовов в setInterval
Вызов setInterval(функция, задержка) ставит функцию на исполнение через указанный интервал времени. Но здесь есть тонкость.
На самом деле пауза между вызовами меньше, чем указанный интервал.
То есть, браузер инициирует запуск функции аккуратно каждые 100мс, без учета времени выполнения самой функции.
Если запуск функции невозможен, потому что браузер занят — она становится в очередь и выполнится, как только браузер освободится.
Изображение ниже иллюстрирует происходящее для функции, которая долго исполняется.
Второй запуск функции происходит сразу же после окончания первого:
Больше одного раза в очередь выполнение не ставится.
Если выполнение функции занимает больше времени, чем несколько запланированных исполнений, то в очереди она всё равно будет стоять один раз. Так что «накопления» запусков не происходит.
На изображении ниже setInterval пытается выполнить функцию в 200 мс и ставит вызов в очередь. В 300 мс и 400 мс таймер пробуждается снова, но ничего не просходит.
Вызов setInterval(функция, задержка) не гарантирует реальной задержки между исполнениями.
Бывают случаи, когда реальная задержка больше или меньше заданной. Вообще, не факт, что будет хоть какая-то задержка.
Повторение вложенным setTimeout
В случаях, когда нужно не просто регулярное повторение, а обязательна задержка между запусками, используется повторная установка setTimeout при каждом выполнении функции.
Ниже — пример, который выдает alert с интервалами 2 секунды между ними.
На временной линии выполнения будут фиксированные задержки между запусками. Иллюстрация для задержки 100мс:
Минимальная задержка таймера
У браузерного таймера есть минимальная возможная задержка. Она меняется от примерно нуля до 4мс в современных браузерах. В более старых она может быть больше и достигать 15мс.
В поведении setTimeout и setInterval с нулевой задержкой есть браузерные особенности.
Реальная частота срабатывания
Срабатывание может быть и гораздо реже В ряде случаев задержка может быть не 4мс, а 30мс или даже 1000мс.
При работе от батареи, в ноутбуке — браузеры тоже могут снижать частоту, чтобы реже выполнять код и экономить заряд батареи. Особенно этим известен IE. Снижение может достигать нескольких раз, в зависимости от настроек. При слишком большой загрузке процессора JavaScript может не успевать обрабатывать таймеры вовремя. При этом некоторые запуски setInterval будут пропущены.
Вывод: на частоту 4мс стоит ориентироваться, но не стоит рассчитывать.
Вывод интервалов в консоль Код, который считает интервалы времени между вызовами, выглядит примерно так:
Трюк setTimeout(func, 0)
Этот трюк достоин войти в анналы JavaScript-хаков.
Дело в том, что setTimeout никогда не выполняет функцию сразу. Он лишь планирует ее выполнение. Но интерпретатор JavaScript начнёт выполнять запланированные функции лишь после выполнения текущего скрипта.
По стандарту, setTimeout в любом случае не может выполнить функцию с задержкой 0. Как мы говорили раньше, обычно задержка составит 4мс. Но главное здесь именно то, что выполнение в любом случае будет после выполнения текущего кода.
Итого
Методы setInterval(func, delay) и setTimeout(func, delay) позволяют запускать func регулярно/один раз через delay миллисекунд.
Объединённый асинхронный JavaScript: Таймауты и интервалы
Необходимые условия: | Базовая компьютерная грамотность, достаточное понимание основ JavaScript. |
---|---|
Цель: | Понимание асинхронных циклов и интервалов, и то как их можно использовать. |
Введение
В течение долгого времени веб-платформа предлагала программистам JavaScript ряд функций, которые позволяли им асинхронно выполнять код по истечении определённого временного интервала и повторно выполнять асинхронный блок кода, пока вы не скажете ему остановиться.
setTimeout() Выполняет указанный блок кода один раз по истечении указанного времени setInterval() Выполняет указанный блок кода несколько раз с определённым интервалом между каждым вызовом. requestAnimationFrame() Современная версия setInterval (). Выполняют указанный блок кода перед тем, как браузер в следующий раз перерисовывает отображение, позволяя запускать анимацию с подходящей частотой кадров независимо от среды, в которой она выполняется.
Асинхронный код, установленный этими функциями, выполняется в основном потоке (по истечении указанного им таймера).
Важно знать, что вы можете (и часто будете) запускать другой код до выполнения вызова setTimeout () или между итерациями setInterval (). В зависимости от того, насколько интенсивно используются эти операции для процессора, они могут ещё больше задержать выполнение асинхронного кода, поскольку любой асинхронный код будет выполняться только после того, как станет доступен основной поток. (Другими словами, когда стек пуст.) вы узнаете больше по этому вопросу по мере изучения этой статьи.
В любом случае эти функции используются для запуска постоянной анимации и другой фоновой обработки на веб-сайте или в приложении. В следующих разделах мы покажем вам, как их можно использовать.
setTimeout()
Как мы ранее отметили, setTimeout () выполняет определённый блок кода один раз по истечении заданного времени. Принимает следующие параметры:
NOTE: Указанное время (или задержка) не является гарантированным временем выполнения, а скорее минимальным временем выполнения. Обратные вызовы, которые вы передаёте этим функциям, не могут выполняться, пока стек в основном потоке не станет пустым.
Как следствие, такой код, как setTimeout (fn, 0), будет выполняться, как только стек будет пуст, а не сразу. Если вы выполните такой код, как setTimeout (fn, 0), но сразу после выполнения цикла, который насчитывает от 1 до 10 миллиардов, ваш колбэк будет выполнен через несколько секунд.
В следующем примере, браузер будет ожидать две секунды перед тем как выполнит анонимную функцию, тогда отобразит сообщение (живой пример, и исходный код):
Указанные вами функции не обязательно должны быть анонимными. Вы можете дать своей функции имя и даже определить её где-нибудь ещё и передать ссылку на функцию в setTimeout (). Следующие две версии фрагмента кода эквивалентны первой:
Это может быть полезно, если у вас есть функция, которую нужно вызывать как по таймауту, так например и в ответ на событие. Но это также может помочь поддерживать ваш код в чистоте, особенно если колбэк тайм-аута занимает больше, чем несколько строк кода.
setTimeout () возвращает значение идентификатора, которое можно использовать для ссылки на тайм-аут позже, например, когда вы хотите его остановить.
Передача параметров в функцию setTimeout ()
Любые параметры, которые вы хотите передать функции, выполняемой внутри setTimeout (), должны быть переданы ей как дополнительные параметры в конце списка.
Например, вы можете реорганизовать предыдущую функцию, чтобы она передавала привет любому имени, переданному ей:
Теперь вы можете передать имя в вызов setTimeout () в качестве третьего параметра:
Очистка таймаутов
Note: См. greeter-app.html для более полной демонстрации, которая позволяет вам указать имя для приветствия и отменить приветствие с помощью отдельной кнопки (см. исходный код).
setInterval()
Очистка интервала
Активное обучение: Создание собственного секундомера!
Вам нужно отображать время, как и раньше, но в этом примере вам нужно:
Несколько подсказок для вас:
Note: Если вы застряли, вы можете увидеть нашу версию (см. также исходный код ).
Что нужно помнить о setTimeout () и setInterval ()
При работе с setTimeout () и setInterval () следует помнить о нескольких вещах. Давайте рассмотрим их.
Рекурсивные таймауты
В приведённом ниже примере используется рекурсивный setTimeout () для запуска переданной функции каждые 100 миллисекунд:
Разница между двумя версиями приведённого выше кода невелика.
Немедленные таймауты
Использование 0 в качестве значения для setTimeout () позволяет планировать выполнение указанной колбэк-функции как можно скорее, но только после того, как будет запущен основной поток кода.
Очистка с помощью clearTimeout() или clearInterval()
clearTimeout () и clearInterval () используют один и тот же список записей для очистки. Интересно, что это означает, что вы можете использовать любой метод для очистки setTimeout () или setInterval ().
requestAnimationFrame()
Note: вы можете найти примеры использования requestAnimationFrame() в этом курсе — например в Рисование графики, and Практика построения объектов.
Метод принимает в качестве аргумента колбэк, который должен быть вызван перед перерисовкой. Это общий шаблон, в котором он используется:
Идея состоит в том, чтобы определить функцию, в которой ваша анимация обновляется (например, ваши спрайты перемещаются, счёт обновляется, данные обновляются или что-то ещё). Затем вы вызываете его, чтобы начать процесс. В конце функционального блока вы вызываете requestAnimationFrame () со ссылкой на функцию, переданной в качестве параметра, и это даёт браузеру указание вызвать функцию снова при следующей перерисовке дисплея. Затем он выполняется непрерывно, поскольку код рекурсивно вызывает requestAnimationFrame ().
Однако, если вы делаете что-то более сложное, включающее объекты, которые не доступны напрямую в the DOM (такие как 2D Canvas API или WebGL ), requestAnimationFrame() предпочтительный вариант в большинстве случаев.
Как быстро работает ваша анимация?
Плавность анимации напрямую зависит от частоты кадров анимации и измеряется в кадрах в секунду (fps). Чем выше это число, тем плавное будет выглядеть ваша анимация до точки.
Поскольку большинство экранов имеют частоту обновления 60 Гц, максимальная частота кадров, к которой вы можете стремиться, составляет 60 кадров в секунду (FPS) при работе с веб-браузерами. Однако большее количество кадров означает больше обработки, которая часто может вызывать заикание и пропуски, также известные как пропадание кадров или заедание.
Если у вас есть монитор с частотой обновления 60 Гц и вы хотите достичь 60 кадров в секунду, у вас есть около 16,7 миллисекунд (1000/60) для выполнения кода анимации для рендеринга каждого кадра. Это напоминание о том, что вам нужно помнить об объёме кода, который вы пытаетесь запустить во время каждого прохождения цикла анимации.
Чем отличается requestAnimationFrame() от setInterval() and setTimeout()?
Давайте поговорим ещё немного о том, чем метод requestAnimationFrame () отличается от других методов, используемых ранее. Глядя на наш код сверху:
Такой же код с использованием setInterval() :
В том числе временная метка
Это полезно, поскольку позволяет запускать вещи в определённое время и в постоянном темпе, независимо от того, насколько быстрым или медленным может быть ваше устройство. Общий шаблон, который вы бы использовали, выглядит примерно так:
Поддержка браузерами
Простой пример
Возьмите базовый HTML шаблон (такой как этот).
Планирование в JavaScript: как применять функции setTimeout и setInterval
Метод setTimeout — это встроенная функция JavaScript, устанавливающая таймер обратного отсчета (в миллисекундах) для выполнения функции обратного вызова по завершении заданного времени.
Представьте, что у вас появилась задача сделать всплывающее окно с приветствием, отображаемое через две секунды после того, как пользователь зашел на сайт. Или же необходимо воспроизвести звук через секунду после объявления победителя в онлайн-игре. В такой ситуации вам не нужно, чтобы функция запускалась немедленно. Надо отложить ее выполнение или запустить через определенный интервал времени.
Метод setTimeout
Метод setTimeout запускается только один раз за вызов, а значит после завершения выполнения функции setTimeout прекращает свою работу.
Основной и наиболее часто используемый синтаксис этой функции:
setTimeout (функция обратного вызова, задержка в миллисекундах)
В этом примере все просто: функция обратного вызова сработает по завершении временной задержки. Давайте рассмотрим пример, как использовать эту функцию на практике:
setTimeout () позволяет нам назначить столько аргументов, сколько необходимо для функции обратного вызова. Предположим, что мы хотим поприветствовать Джона — пользователя, вошедшего в нашу систему. Для этого нам необходимо просто добавить аргументы в конец списка параметров setTimeout :
setTimeout (функция обратного вызова, задержка в миллисекундах, arg1)
Посмотрим, как это реализовать:
Глядя на код предыдущего примера у вас может возникнуть соблазн передать аргументы в качестве параметров функции обратного вызова. Например:
SetTimeout (Greet(loggedInUser), 2000);
Функция clearTimeout
Иногда бывают ситуации, когда нам нужно отменить начавшуюся временную задержку. Что делать в этом случае? Метод setTimeout () возвращает ключ, позволяющий ссылаться на выполнение callback-функции. Мы можем использовать этот ключ как кнопку прерывания, тем самым взяв в свои руки контроль над самим механизмом тайм-аута. Например:
const timerId = setTimeout(Greet, 5000, loggedInUser);
Здесь timerId — тот самый ключ, используемый для ссылки на текущий таймер. Чтобы отменить тайм-аут, этот ключ нужно передать функции clearTimeout () в качестве параметра:
При использовании clearTimeout() происходит отмена таймера, и функция обратного вызова внутри метода не сработает.
Метод setInterval
Метод setInterval используется для повторного выполнения функции по истечении определенного периода времени. Схема построения этого метода следующая:
В этом случае задержка — это время в миллисекундах, в течение которого таймер должен задерживать последовательные выполнения функции.
В примере метод будет повторно выводить в консоль Hello John каждые три секунды.
Вернемся к нашему примеру:
Функция sayHello будет выполнена всего три раза.
Вы можете задаться вопросом, почему оператор if находится внутри функции, а не за ее пределами, вот так:
Для того чтобы ответить на него, необходимо разобраться, как JavaScript выполняет рассматриваемые нами методы. В отличие от других языков, в JS — один поток для решения задач, выполняемый построчно. Это значит, что каждая строка кода должна завершить свое выполнение, прежде чем переходить к следующей. Другими словами, выполнение остального кода блокируется.
Но есть операции ввода-вывода, которые не блокируются. Они обрабатываются базовым механизмом. К ним относятся:
Следовательно, если бы мы прописали реализацию метода вторым способом, таймер не перестал бы работать, ведь после выполнения строки let timerID = setInterval(sayHello, 3000, «John») JavaScript перешел бы к следующему блоку кода if (counter === 3)
Рекурсивный setTimeout
Метод setInterval запускает функцию каждые n миллисекунд, не обращая внимания на то, когда она завершится.
Если функция каждый раз срабатывает с одним и тем же временным интервалом, тогда все в порядке:
Функция срабатывает с одним и тем же временным интервалом
Но бывают случаи, когда время выполнения меняется в зависимости от условий сети, например:
Время выполнения может меняться
Или перекрывает следующее:
Время выполнения перекрывает следующее за ним
Чтобы избежать последствий подобных случаев, вы можете запланировать рекурсивный вызов setTimeout по завершении функции обратного вызова.
Для наглядности рассмотрим распространенный пример сервиса по отправке запросов на сервер, который при перегрузке последнего увеличивал бы интервал следующего запроса:
C рекурсивным setTimeout мы можем изменять последующий вызов, исходя из результата предыдущего.
Фиксированная временная задержка
Другими словами, такой подход дает фиксированную временную задержку, ведь новый вызов планируется, когда закончился вызов перед ним.
Что надо помнить
Но здесь есть свои подводные камни. Функция обратного вызова все равно будет взаимодействовать с внешними переменными, иногда занимающими больше памяти, чем она сама. В таком случае при отсутствии большой необходимости в частом вызове функции есть смысл его отменить.
Нулевая задержка
В официальной документации к setTimeout написано, что delay (временная задержка) — необязательное поле. Если оно не указано, то таймер равен нулю и функция обратного вызова срабатывает «мгновенно или как можно скорее». Такая странная формулировка обусловлена наличием у браузера своего таймера с минимально возможной задержкой. Она может варьироваться от 4 мс в современных браузерах, до 15 мс в более старых. Но срабатывать она может и реже — с опозданием вплоть до 1000 мс.
Это связано с особенностью некоторых браузеров запускать таймер на неактивной странице. Этим грешат Google Chrome, Firefox, IE10. На частоту запуска функции также влияют загрузка процессора и режим экономии энергии разряженной батареи при работе на ноутбуке.
Пример использования метода
Рассмотрим простой пример реализации всплывающего сообщения, появляющегося спустя 2000 миллисекунд после наведения мышкой на элемент. Условие: если курсор убрали раньше, сообщение не нужно показывать.
Резюмируем
Простой метод измерения реальной скорости загрузки страниц у посетителей сайта
Как можно закэшировать данные и выиграть в производительности
Как работает Server-Sent API с примерами
Примеры применения Javascript в Nginx’e
Как просто сделать удобный дебаг и не лазить в код или как бородатые хакеры перехватывают ajax-запросы, нарушая вашу безопасность.
В своем блоге индийский разработчик Шашват Верма (Shashwat Verma) рассказал, как преобразовать веб-сайт или веб-страницу в прогрессивное веб-приложение (PWA).