{"id":15379656,"url":"https://github.com/dfilatov/lrt","last_synced_at":"2025-07-22T21:06:28.272Z","repository":{"id":34944627,"uuid":"192724412","full_name":"dfilatov/lrt","owner":"dfilatov","description":"Scheduler for long-running tasks inside browsers and Node.JS","archived":false,"fork":false,"pushed_at":"2023-01-27T08:51:09.000Z","size":1060,"stargazers_count":66,"open_issues_count":6,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-06-30T06:01:57.697Z","etag":null,"topics":["chunk","cooperative-scheduling","generators","scheduler","task","thread","typescript","unit-of-work"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dfilatov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-19T12:05:06.000Z","updated_at":"2024-07-26T21:21:04.000Z","dependencies_parsed_at":"2023-02-15T07:16:35.286Z","dependency_job_id":null,"html_url":"https://github.com/dfilatov/lrt","commit_stats":null,"previous_names":["dfilatov/chf"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/dfilatov/lrt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfilatov%2Flrt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfilatov%2Flrt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfilatov%2Flrt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfilatov%2Flrt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dfilatov","download_url":"https://codeload.github.com/dfilatov/lrt/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfilatov%2Flrt/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266572661,"owners_count":23950069,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-22T02:00:09.085Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chunk","cooperative-scheduling","generators","scheduler","task","thread","typescript","unit-of-work"],"created_at":"2024-10-01T14:19:35.238Z","updated_at":"2025-07-22T21:06:28.244Z","avatar_url":"https://github.com/dfilatov.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LRT\n![Tests](https://github.com/dfilatov/lrt/workflows/Tests/badge.svg?branch=master)\n[![NPM Version](https://img.shields.io/npm/v/lrt.svg?style=flat-square)](https://www.npmjs.com/package/lrt)\n\n## What is it?\nLRT is a scheduler for long-running tasks inside browsers and Node.JS.\n\n## Key features\n  * API to split long-running tasks into units of work via Iterator protocol\n  * Ability to run multiple long-running tasks concurrently with coordinating their execution via coopeative scheduling\n  * Ability to abort outdated tasks\n  * Ability to specify chunk budget and maximize its utilization\n  * Built-in set of predefined chunk schedulers\n  * Ability to implement custom chunk scheduler\n  * Supports generators for tasks splitting\n  * Works in both Browser and Node.JS platforms\n  * Small, fast and dependency-free\n\nThe main idea is to split long-running tasks into small units of work joined into chunks with limited budget of execution time. Units of works are executed synchronously until budget of current chunk is reached, afterwards thread is unblocked until scheduler executes next chunk and so on until all tasks have been completed.\n\n\u003cimg width=\"1175\" alt=\"lrt\" src=\"https://user-images.githubusercontent.com/67957/60708338-84587f80-9f16-11e9-980f-ed0b715e7b4e.png\"\u003e\n\n## Table of Contents\n  * [Installation](#installation)\n  * [Usage](#usage)\n  * [API](#api)\n    * [Scheduler](#scheduler)\n    * [Task iterator](#task-iterator)\n    * [Chunk scheduler](#chunk-scheduler)\n  * [Questions and answers](#questions-and-answers)\n  * [Example](#example)\n\n## Installation\n```\n$ npm install lrt\n```\n**Note**: LRT requires native [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) and [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) so if your environment doesn't support them, you will have to install any suitable polyfills as well.\n\n## Usage\n```ts\n// with ES6 modules\nimport { createScheduler } from 'lrt';\n\n// with CommonJS modules\nconst { createScheduler } = require('lrt');\n```\n\n## API\n\n```ts\nconst scheduler = createScheduler(options);\n```\n  * `options` (`object`, optional)\n  * `options.chunkBudget` (`number`, optional, default is `10`) An execution budget of chunk in milliseconds.\n  * `options.chunkScheduler` (`string|object`, optional, default is `'auto'`) A [chunk scheduler](#chunk-scheduler), can be `'auto'`, `'idleCallback'`, `'animationFrame'`, `'immediate'`, `'timeout'` or object representing custom scheduler.\n\nReturned `scheduler` has two methods:\n  * `const task = scheduler.runTask(taskIterator)`\n  Runs task with a given [taskIterator](#task-iterator) and returns task (promise) resolved or rejected after task has completed or thrown an error respectively.\n  * `scheduler.abortTask(task)` Aborts task execution as soon as possible (see diagram above).\n\n### Scheduler\nScheduler is responsible for tasks running, aborting and coordinating order of execution of their units. It accumulates statistics while tasks are being run and tries to maximize budget utilization of each chunk. If a unit of some task has no time to be executed in the current chunk, it will get higher priority to be executed in the next chunk.\n\n### Task iterator\nTask iterator should be an object implementing [Iterator protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterator_protocol). The most convenient way to build iterator is to use generators (calling a generator function returns a generator object implementing iterator protocol). Another option is to build your own object implementing iterator protocol.\n\nExample with generator:\n```ts\nfunction* generator() {\n    let i = 0;\n\n    while(i \u003c 10) {\n        doCurrentPartOfTask(i);\n        i++;\n        yield;\n    }\n\n    return i;\n}\n\nconst iterator = generator();\n```\n\nExample with object implementing iterator protocol:\n```ts\nconst iterator = {\n    next(i = 0) {\n        doCurrentPartOfTask(i);\n\n        return {\n            done: i \u003c 10,\n            value: i + 1\n        };\n    }\n};\n```\nFor convenience LRT passes a previous value as an argument to the `next` method. The first `next` call doesn't obtain this argument and default value can be specified as an initial one.\n\n### Chunk scheduler\nChunk scheduler is utilized internally to schedule execution of the next chunk of units. Built-in options:\n  * `'auto'` (by default) LRT will try to detect the best available option for your current environment.\nIn browsers any of `'idleCallback'` / `'animationFrame'` / `'postMessage'` option will be used depending on their availability, or `'immediate'` inside NodeJS. If nothing suitable is available, `'timeout'` option will be used as a fallback.\n  * `'idleCallback'` LRT will try to use [Background Tasks API](https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API). If it's not available, `'timeout'` option will be used as a fallback.\n  * `'animationFrame'` LRT will try to use [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame). If your tasks need to change the DOM, you should use it instead `'auto'` or `'idleCallback'`. If it's not available, `'timeout'` option will be used as a fallback.\n  * `'postMessage'` LRT will try to use [postMessage](https://developer.mozilla.org/ru/docs/Web/API/Window/postMessage). If it's not available, `'timeout'` option will be used as a fallback.\n  * `'immediate'` LRT will try to use [setImmediate](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate). If it's not available, `'timeout'` option will be used as a fallback.\n  * `'timeout'` LRT will use [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) with zero delay.\n\nAlso you can specify your own implementation of scheduler.\n\n#### Custom chunk scheduler\nCustom scheduler should implement two methods:\n  * `request(fn)` (required) Accepts function `fn` and returns `token` for possible aborting via `cancel` method (if it is specified)\n  * `cancel(token)` (optional) Accepts `token` and cancels scheduling\n\nFor example, let's implement scheduler which runs next chunk of units in ~100 milliseconds after previous chunk has ended:\n```ts\nconst customChunkScheduler = {\n    request: fn =\u003e setTimeout(fn, 100),\n    cancel: token =\u003e clearTimeout(token)\n};\n\nconst scheduler = createScheduler({\n    chunkScheduler: customChunkScheduler\n});\n```\n\n## Questions and answers\n\n**What if unit takes more time than chunk budget?**\n\nMore likely this means that chunk budget is too small or you need to split your tasks into smaller units. Anyway LRT guarantees  at least one of units of some task will be executed within each chunk.\n\n**Why not just move long-running task into Web Worker?**\n\nDespite the fact that Web Workers are very useful, they do have a cost: time to instantiate/terminate workers, message latency on large workloads, need for coordination between threads, lack of access the DOM. Nevertheless, you can use LRT inside Web Worker and get the best of both worlds: do not affect main thread and have ability to abort outdated tasks.\n\n## Example\n```ts\n// Create scheduler\nconst scheduler = createScheduler();\n\n// Imitate a part of some long-running task taking 80ms in the whole\nfunction doPartOfTask1() {\n    const startTime = Date.now();\n\n    while(Date.now() - startTime \u003c 8) {}\n}\n\n// Imitate a part of another long-running task taking 100ms in the whole\nfunction doPartOfTask2() {\n    const startTime = Date.now();\n\n    while(Date.now() - startTime \u003c 5) {}\n}\n\nfunction* task1Generator() {\n    let i = 0;\n\n    while(i \u003c 10) { // 10 units will be executed\n        doPartOfTask1();\n        i++;\n        yield;\n    }\n\n    return i;\n}\n\nfunction* task2Generator() {\n    let i = 0;\n\n    while(i \u003c 20) { // 20 units will be executed\n        doPartOfTask2();\n        i++;\n        yield;\n    }\n\n    return i;\n}\n\n// Run both tasks concurrenly\nconst task1 = scheduler.runTask(task1Generator());\nconst task2 = scheduler.runTask(task2Generator());\n\n// Wait until first task has been completed\ntask1.then(\n    result =\u003e {\n        console.log(result); // prints \"10\"\n    },\n    err =\u003e {\n        console.error(err);\n    }\n);\n\n// Abort second task in 50 ms, it won't be completed\nsetTimeout(() =\u003e scheduler.abortTask(task2), 50);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfilatov%2Flrt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdfilatov%2Flrt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfilatov%2Flrt/lists"}