TaskLimiter

Limits the number of Promises that are running, and optionally queued as well.

const tl = new TaskLimiter(1,2);

// preferred usage
await tl.add(some_function);
// you can leave off the await if the queued_limit is Infinity (the default), or to avoid race conditions

// not recommended, but allowed
tl.add(already_running_promise);

// more manual control; works as well, but only if the `add` call is sync;
// will still need to await tl.add if you want to know when it was queued
await tl.tasksBelow(6)
tl.add(some_function);
// some additional wrappers available for the xxxBelow methods
tl.runningEmpty().then(() => {
	process.exit();
})

After TaskLimiter#add is called, a task goes through four phases. A phase will be skipped if it can proceed directly to the next:

  1. The tasks is blocked until it can be added to the queue. You can have multiple tasks blocked at the same time for use cases like multithreading or uncoordinated code sections, but in general you should not add more tasks until the previous has unblocked.
  2. Once the number of queued tasks falls below TaskLimiter#queued_limit, the task is moved from blocked to queued.
  3. Once the number of running tasks falls below TaskLimiter#running_limit, the task is moved from queued to running. If the tasks is a function, it is called here
  4. The task finishes, possibly asynchronously, and is removed from running

How does queueing tasks recursively work? Take TaskLimiter(1, 0) for example: if the running task tries to add another task it could get deadlocked. Here's how to handle recursive tasks:

  1. If the child is running in-place of the parent task, meaning the parent blocks while waiting for the child to finish, you do not need to use the TaskLimiter for the child. Just run the child function/promise directly in the parent.
  2. If for whatever reason the child does need to use the TaskLimiter, for example multiple children running simultaneously, or you want to limit the total parent+child recursion, you can run into deadlock scenarios. The easiest way to prevent this is to increment TaskLimiter#running_limit before doing your child tasks, and then decrement when done. For tail recursion scenarios, where the parent would immediately exit when the children have been queued, you can use await add(task, true); if your tail recursion could run into recursion stack limit issues, this could be a good alternative to #1.

Constructor

new TaskLimiter(running_limitopt, queued_limitopt)

Create a new task limiter

Parameters:
NameTypeAttributesDefaultDescription
running_limitnumber<optional>
10

Initial value for TaskLimiter#running_limit property

queued_limitnumber<optional>
Infinity

Initial value for TaskLimiter#queued_limit property

Classes

TaskLimiter

Members

(readonly) blocked :number

Number of blocked tasks that have yet to be queued

Type:
  • number

(readonly) queued :number

Number of queued tasks

Type:
  • number

queued_limit :number

Limits the number of tasks that can be queued. TaskLimiter#add will block until the queue opens up more slots. If you dynamically change this value, the queued task count could rise above this limit temporarily.

Set this to Infinity for unlimited queue, and no blocked tasks. If not Infinity, make sure you await the TaskLimiter#add result, otherwise an internal tasks_blocked list will be filled. This can be zero to have no queue, blocking until a task can be run.

Type:
  • number

(readonly) running :number

Number of running tasks

Type:
  • number

running_limit :number

Limits the number of async tasks that can be running at the same time. If you

  • add a Promise directly to add method
  • dynamically change this value

then the running task count could rise above this limit temporarily.

Type:
  • number

(readonly) tasks :number

Number of tasks, either running and queued

Type:
  • number

Methods

add(task, nonblocking) → {Promise}

Add a task to be queued

Parameters:
NameTypeDefaultDescription
taskPromise | function

This can be a promise, in which case it is considered running already. Otherwise it should be a function representing a task. If the function returns a promise, it is considered an async task and will handled accordingly; if it returns any other type, it is assumed to be a synchronous task.

nonblockingbooleanfalse

If true, for a task that is not yet running this will send it directly to the queue, disregarding any TaskLimiter#queued_limit. You might use this if you add tasks in a tail recursive manner, knowing that a running slot will open up and bring the queued length back to queued_limit.

Returns:

resolves when the task is queued or run immediately

Type: 
Promise

blockedBelow(limitopt, nullable) → {Promise}

Returns promise that resolves when TaskLimiter#blocked falls below limit

Parameters:
NameTypeAttributesDefaultDescription
limitnumber<optional>
<nullable>
null

null checks if it is below one (e.g. no blocked tasks)

Returns:
Type: 
Promise

blockedEmpty() → {Promise}

Returns promise that resolves when there are no blocked tasks

Returns:
Type: 
Promise

canHandle() → {Promise}

Returns a promise which resolves when a task can be queued or run. See TaskLimiter#canRun and TaskLimiter#canQueue methods. This is an alias for tasksBelow(null) call.

Returns:
Type: 
Promise

canQueue() → {Promise}

Returns a promise which resolves when a task can be queued. This is an alias for queuedBelow(null) call.

Returns:
Type: 
Promise

canRun() → {Promise}

Returns a promise which resolves when a task can be run. This is an alias for runningBelow(null) call.

Returns:
Type: 
Promise

queuedBelow(limitopt, nullable) → {Promise}

Returns promise that resolves when TaskLimiter#queued falls below limit

Parameters:
NameTypeAttributesDefaultDescription
limitnumber<optional>
<nullable>
null

null checks if it is below TaskLimiter#queued_limit

Returns:
Type: 
Promise

queuedEmpty() → {Promise}

Returns promise that resolves when there are no queued tasks

Returns:
Type: 
Promise

runningBelow(limitopt, nullable) → {Promise}

Returns promise that resolves when TaskLimiter#running falls below limit

Parameters:
NameTypeAttributesDefaultDescription
limitnumber<optional>
<nullable>
null

null checks if it is below TaskLimiter#running_limit

Returns:
Type: 
Promise

runningEmpty() → {Promise}

Returns promise that resolves when there are no running tasks

Returns:
Type: 
Promise

tasksBelow(limitopt, nullable) → {Promise}

Returns promise that resolves when TaskLimiter#tasks falls below limit

Parameters:
NameTypeAttributesDefaultDescription
limitnumber<optional>
<nullable>
null

null checks if it is below the sum of TaskLimiter#running_limit and TaskLimiter#queued_limit

Returns:
Type: 
Promise

tasksEmpty() → {Promise}

Returns promise that resolves when there are no running or queued tasks

Returns:
Type: 
Promise