npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

lib-loop

v0.1.3

Published

Simple node.js library for work with async loops

Readme

Простая нативная работа с циклами.

Библиотка javascript/typescript (ES6) для node.js.

  • бесконечный цикл (loop) с возможностью выхода,
  • управляемый цикл (next),
  • игровой цикл (game loop),
  • асинхронные циклы,
  • асинхронная пауза.

Установка

npm install lib-loop

или

yarn add lib-loop

Начало работы

Можно импортировать методы как с отдельные функции:

import { loop, wait } from 'lib-loop';

loop...
wait...

или вызывать их как статические методы объекта:

import * as loop from 'lib-loop';

loop.loop...
loop.wait...

Список методов

async iterate(callback: () => void, maxIterations, milliseconds = 0): void**

Запускает цикл.

В цикле вызывается функция callback.

Второй аргумент задает количество итераций. Если задать ноль или отрицательное число, то цикл не запустится.

Третьим необязательным аргументом можно передать задержку между итерациями.

async loop(callback: (deltaTime: number) => boolean, milliseconds = 0, tps = 0): void**

Запускает бесконечный цикл.

В цикле вызывается функция callback.

Функция callback принимает необязательный аргумент:

  • deltaTime - время в секундах, прошедшее с момента последнего обновления итерации цикла,

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

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

Цикл работает, пока функция callback возвращает true.

Цикл завершится, когда функция callback вернет false.

После определения callback, функция цикла может принять два необязательных аргумента:

  • milliseconds - принудительная задержка между итерациями в миллисекундах,

  • tps - число итераций (тиков) в секунду.

Если указать tps, то в итерациях будут срабатывать автоматические паузы.

async frameLoop(callback: (deltaTime: number) => boolean): void

Бесконечный цикл, использует оптимизацию браузера через requestAnimationFrame:

  • синхронизацию с частотой обновления экрана,

  • экономию ресурсов в неактивном состоянии.

Метод requestAnimationFrame является распространенным решением в игровых движках.

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

async frameRate(milliseconds = 1000): number

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

Аргумент milliseconds задает время, в течение которого идет тест.

Чем больше время, тем точнее расчет.

По-умолчанию установлено значение 100. Это минимально рекомендуемое значение. Оно не будет точным и даст приблизительную оценку с некоторой погрешностью. Но при том оно не занимает много времени.

async wait(milliseconds: number): void**

Пауза для асинхронного кода.

Класс управляемого цикла

constructor(main: (this) => void): void

Создает цикл.

В цикле задается функция main, которая будет вызываться в каждой итерации.

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

context: any

Свойство, хранящее любые пользовательские состояния цикла.

По-умолчанию - пустой объект.

iterate: number

Свойство, хранящее порядковый номер итерации, начиная с 0.

Значение -1 показывает, что ни одна итерация еще не была запущена.

async start(callback: (this) => void): void**

Запускает цикл.

Перед запуском цикла вызывается функция callback.

По-умолчанию callback задан как пустая функция.

async stop(callback: (this) => void): void**

Останавливает цикл.

После остановки вызывается функция callback.

По-умолчанию callback задан как пустая функция.

async next(callback: (this) => void): void**

Продолжает цикл, переходит к следующей итерации.

Перед запуском вызывается функция callback.

По-умолчанию callback задан как пустая функция.

async wait(milliseconds: number): void**

Алиас метода wait, который можно вызвать как метод класса.

Примеры

Бесконечный цикл

Предположим, у нас есть асинхронная функция main:

async function main() {
  if (...) {
    return false;
  }
  return true;
}

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

loop(main);

Эквивалентный способ:

(async () => loop(main))();

Если функция main синхронная:

function main(): boolean {
  if (...) {
    return false;
  }
  return true;
}

Ее нужно запускать внутри асинхронного вызова:

loop(async () => main());

Допускается запускать внутри синхронный вызов:

loop(() => {
  ...
  return true;
});

или синхронную функцию:

loop(main);

Несколько циклов

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

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

await loop(...);
await loop(...);

или:

(async () => {
  await loop(...);
  await loop(...);
})();

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

Обработку ошибок можно сделать так:

loop(() => main().catch((e) => {
  console.error(e);
  return false;
}));

Цикл с заданным количеством итераций

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

async function main() {
  ...
}

iterate(main, 10);

Пример управляемого цикла

Создадим простой управляемый цикл.

Определим функцию, которая будет выполняться в каждой итерации:

async function main(self: Infinite) {
  console.log(self.iterate);

  if (self.iterate >= 9) {
    self.stop();
  }

  self.next();
}

Далее создадим цикл и запустим его:

const infinite = new Infinite(main);

infinite.start();

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

async function main(self: Infinite) {
  console.log(self.iterate);

  if (self.iterate >= 9) {
    self.stop(() => {
      console.log('Цикл остановлен')
    });
  }

  await self.wait(1000);
  self.next();
}

const infinite = new Infinite(main);

infinite.start(() => {
  console.log('Цикл запущен')
});

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

async function main(self: Infinite) {
  const { iterate, context } = self;

  console.log(iterate);

  if (iterate >= 9 && !context.paused) {
    self.stop(async () => {
      context.paused = true;
      console.log('Цикл временно приостановлен');

      await self.wait(1000);

      self.start(() => {
        console.log('Цикл продолжен');
      });
    });
  }

  if (iterate >= 19 && !context.stopped) {
    self.stop(() => {
      context.stopped = true;
      console.log('Цикл завершен');
    });
  }

  await self.wait(100);
  self.next();
};

const infinite = new Infinite(main);

infinite.context.paused = false;
infinite.context.stopped = false;

infinite.start(() => {
  console.log('Цикл запущен');
});

Задержка между итерациями

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

loop(main, 1000)

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

async function main() {
  if (...) {
    return false;
  }

  await wait(1000);

  return true;
}

Пауза в цикле

Если вам нужно временно приостановить выполнение команд внутри цикла, самым простым и правильным способом будет пропуск вызова этих команд по какому-либо условию.

Вот пример с активацией флага paused:

const paused = true;

loop(() => {
  if (paused) {
    return;
  }
  main();
})

Можно также перенести эту логику внутрь вызываемой функции main:

const paused = true;

const main = async () => {
  if (paused) {
    return true;
  }
  ...
}

Игровой цикл

В этом примере мы используем цикл frameLoop в качестве игрового цикла.

Создадим игровой объект, который будет перемещаться по координате x со скоростью speed пикселей в секунду.

class GameObject {
  x: number;
  speed: number;

  constructor() {
    this.x = 0;
    this.speed = 100;
  }
}

Зададим для этого класса метод update, который будет вызываться в каждом цикле.

Первым аргументом он принимает deltaTime - смещение по времени в секундах между текущей и предыдущей итерацией.

Его мы будем использовать для расчета и корректировки скорости. Мы также посчитаем fps и выведем в консоль.

  update(deltaTime: number) {
    this.x += this.speed * deltaTime;
    const fps = 1 / deltaTime;
    console.log(fps);
  }

Запустим наш цикл.

const gameObject = new GameObject();

frameLoop(gameObject.update);

Когда мы умножаем скорость speed на deltaTime, мы получаем количество пикселей, на которое должен переместиться объект за этот кадр.

Мы знаем, что frameLoop привязан к частоте кадров fps.

Например, при fps 60 кадров в секунду deltaTime составит примерно 0.0166. За один цикл tick объект переместится на 200 * 0.0166 = 3.32 пикселя. Для 1 секунды (60 кадров) это составит 3.32 * 60 = 199.2 пикселей.

Если же fps будет, например, 120 кадров в секунду, то deltaTime составит примерно 0.0083. За один цикл tick объект переместится на 200 * 0.0083 = 1.66 пикселя. Для 1 секунды (120 кадров) это составит 1.66 * 120 = 199.2 пикселя.

Если же вдруг fps снизится, например, до 30 кадров в секунду, то deltaTime составит примерно 0.0333. За один цикл tick объект переместится на 200 * 0.0333 = 6.66 пикселя. Для 1 секунды (120 кадров) это составит 6.66 * 30 = 199.8 пикселей.

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

Без deltaTime скорость перемещения будет сильно меняться в зависимости от часты кадров.

Версии

0.1.2

Теперь frameLoop поддерживает браузерный метод visibilitychange, который отслеживает, когда вкладка или окно меняет видимость.

При потере видимости, frameLoop полностью прерывает выполнение цикла, но при восстановлении видимости, запускает цикл заново.

Это позволяет значительно сэкономить ресурсы в фоновом режиме.

0.1.1

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

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

Если вы хотите выбрать цикл с гибкими настройками - используйте loop.

Лицензия

Лицензия MIT, 2025