can-queues
Version:
A light weight JavaScript task queue
166 lines (141 loc) • 4.73 kB
JavaScript
"use strict";
var Queue = require( "./queue" );
var PriorityQueue = function () {
Queue.apply( this, arguments );
// A map of a task's function to the task for that function.
// This is so we can prevent duplicate functions from being enqueued
// and so `flushQueuedTask` can find the task and run it.
this.taskMap = new Map();
// An "array-of-arrays"-ish data structure that stores
// each task organized by its priority. Each object in this list
// looks like `{tasks: [...], index: 0}` where:
// - `tasks` - the tasks for a particular priority.
// - `index` - the index of the task waiting to be prioritized.
this.taskContainersByPriority = [];
// The index within `taskContainersByPriority` of the first `taskContainer`
// which has tasks that have not been run.
this.curPriorityIndex = Infinity;
// The index within `taskContainersByPriority` of the last `taskContainer`
// which has tasks that have not been run.
this.curPriorityMax = 0;
this.isFlushing = false;
// Manage the number of tasks remaining to keep
// this lookup fast.
this.tasksRemaining = 0;
};
PriorityQueue.prototype = Object.create( Queue.prototype );
PriorityQueue.prototype.constructor = PriorityQueue;
PriorityQueue.prototype.enqueue = function ( fn, context, args, meta ) {
// Only allow the enqueing of a given function once.
if ( !this.taskMap.has( fn ) ) {
this.tasksRemaining++;
var isFirst = this.taskContainersByPriority.length === 0;
var task = {
fn: fn,
context: context,
args: args,
meta: meta || {}
};
var taskContainer = this.getTaskContainerAndUpdateRange( task );
taskContainer.tasks.push( task );
this.taskMap.set( fn, task );
//!steal-remove-start
if(process.env.NODE_ENV !== 'production') {
this._logEnqueue( task );
}
//!steal-remove-end
if ( isFirst ) {
this.callbacks.onFirstTask( this );
}
}
};
// Given a task, updates the queue's cursors so that `flush`
// will be able to run the task.
PriorityQueue.prototype.getTaskContainerAndUpdateRange = function ( task ) {
var priority = task.meta.priority || 0;
if ( priority < this.curPriorityIndex ) {
this.curPriorityIndex = priority;
}
if ( priority > this.curPriorityMax ) {
this.curPriorityMax = priority;
}
var tcByPriority = this.taskContainersByPriority;
var taskContainer = tcByPriority[priority];
if ( !taskContainer ) {
taskContainer = tcByPriority[priority] = {tasks: [], index: 0};
}
return taskContainer;
};
PriorityQueue.prototype.flush = function () {
// Only allow one task to run at a time.
if ( this.isFlushing ) {
return;
}
this.isFlushing = true;
while ( true ) {
// If the first prioritized taskContainer with tasks remaining
// is before the last prioritized taskContainer ...
if ( this.curPriorityIndex <= this.curPriorityMax ) {
var taskContainer = this.taskContainersByPriority[this.curPriorityIndex];
// If that task container actually has tasks remaining ...
if ( taskContainer && ( taskContainer.tasks.length > taskContainer.index ) ) {
// Run the task.
var task = taskContainer.tasks[taskContainer.index++];
//!steal-remove-start
if(process.env.NODE_ENV !== 'production') {
this._logFlush( task );
}
//!steal-remove-end
this.tasksRemaining--;
this.taskMap["delete"]( task.fn );
task.fn.apply( task.context, task.args );
} else {
// Otherwise, move to the next taskContainer.
this.curPriorityIndex++;
}
} else {
// Otherwise, reset the state for the next `.flush()`.
this.taskMap = new Map();
this.curPriorityIndex = Infinity;
this.curPriorityMax = 0;
this.taskContainersByPriority = [];
this.isFlushing = false;
this.callbacks.onComplete( this );
return;
}
}
};
PriorityQueue.prototype.isEnqueued = function ( fn ) {
return this.taskMap.has( fn );
};
PriorityQueue.prototype.flushQueuedTask = function ( fn ) {
var task = this.dequeue(fn);
if(task) {
//!steal-remove-start
if(process.env.NODE_ENV !== 'production') {
this._logFlush( task );
}
//!steal-remove-end
task.fn.apply( task.context, task.args );
}
};
PriorityQueue.prototype.dequeue = function(fn){
var task = this.taskMap.get( fn );
if ( task ) {
var priority = task.meta.priority || 0;
var taskContainer = this.taskContainersByPriority[priority];
var index = taskContainer.tasks.indexOf( task, taskContainer.index );
if ( index >= 0 ) {
taskContainer.tasks.splice( index, 1 );
this.tasksRemaining--;
this.taskMap["delete"]( task.fn );
return task;
} else {
console.warn("Task", fn, "has already run");
}
}
};
PriorityQueue.prototype.tasksRemainingCount = function () {
return this.tasksRemaining;
};
module.exports = PriorityQueue;