DoneJS StealJS jQuery++ FuncUnit DocumentJS
4.3.0
5.0.0 3.13.1 2.3.35
  • About
  • Guides
  • API Docs
  • Community
  • Contributing
  • Bitovi
    • Bitovi.com
    • Blog
    • Design
    • Development
    • Training
    • Open Source
    • About
    • Contact Us
  • About
  • Guides
  • API Docs
    • Observables
      • can-bind
      • can-compute
      • can-debug
      • can-define
      • can-define/list/list
      • can-define/map/map
      • can-define-backup
      • can-define-stream
      • can-define-stream-kefir
      • can-event-queue
      • can-kefir
      • can-list
      • can-map
      • can-map-define
      • can-observation
      • can-observation-recorder
      • can-observe
      • can-simple-map
      • can-simple-observable
      • can-stream
      • can-stream-kefir
      • can-value
    • Data Modeling
      • can-connect
      • can-connect-feathers
      • can-fixture
      • can-fixture-socket
      • can-ndjson-stream
      • can-set
    • Views
      • can-component
      • can-stache
      • can-stache-bindings
      • can-stache-converters
      • can-stache-route-helpers
      • can-view-autorender
      • can-view-callbacks
      • can-view-import
      • can-view-live
      • can-view-model
      • can-view-nodelist
      • can-view-parser
      • can-view-scope
      • can-view-target
      • react-view-model
      • react-view-model/component
      • steal-stache
    • Routing
      • can-deparam
      • can-param
      • can-route
      • can-route-hash
      • can-route-mock
      • can-route-pushstate
    • JS Utilities
      • can-assign
      • can-define-lazy-value
      • can-diff
      • can-globals
      • can-join-uris
      • can-key
      • can-key-tree
      • can-make-map
      • can-parse-uri
      • can-queues
        • queue types
          • CompletionQueue
          • can-queues.PriorityQueue
          • can-queues.Queue
        • queues
          • notifyQueue
          • deriveQueue
          • domUIQueue
          • mutateQueue
        • methods
          • batch.start
          • batch.stop
          • enqueueByQueue
          • log
          • logStack
      • can-string
      • can-string-to-any
      • can-util
      • can-zone
      • can-zone-storage
    • DOM Utilities
      • can-ajax
      • can-attribute-encoder
      • can-child-nodes
      • can-control
      • can-dom-data
      • can-dom-events
      • can-dom-mutate
      • can-event-dom-enter
      • can-event-dom-radiochange
      • can-fragment
    • Data Validation
      • can-define-validate-validatejs
      • can-validate
      • can-validate-interface
      • can-validate-legacy
      • can-validate-validatejs
    • Typed Data
      • can-cid
      • can-construct
      • can-construct-super
      • can-data-types
      • can-namespace
      • can-reflect
      • can-reflect-dependencies
      • can-reflect-promise
      • can-types
    • Polyfills
      • can-symbol
      • can-vdom
    • Core
    • Infrastructure
      • can-global
      • can-test-helpers
    • Ecosystem
    • Legacy
  • Community
  • Contributing
  • GitHub
  • Twitter
  • Chat
  • Forum
  • News
Bitovi

can-queues

  • Edit on GitHub

A light weight queue system for scheduling tasks.

Object

The can-queues package exports an object with queue constructors, shared instances, and helpers methods. The following describes the properties of the can-queues object:

{
    Queue,           // The Queue type constructor

    PriorityQueue,   // The PriorityQueue type constructor

    CompletionQueue, // The CompletionQueue type constructor

    notifyQueue,     // A Queue used to tell objects that
    // derive a value that they should be updated.

    deriveQueue,     // A PriorityQueue used update values.

    domUIQueue,      // A CompletionQueue used for updating the DOM or other
    // UI after state has settled, but before user tasks

    mutateQueue,     // A Queue used to register tasks that might
    // update other values.

    batch: {
        start,     // A function used to prevent the automatic flushing
        // of the NOTIFY_QUEUE.

        stop       // A function used to begin flushing the NOTIFY_QUEUE.
    },

    enqueueByQueue, // A helper function used to queue a bunch of tasks.

    stack,         // A function that returns an array of all the queue
    // tasks up to this point of a flush for debugging.
    // Returns an empty array in production.

    logStack,      // A function that logs the result of `this.stack()`.
    // Doesn't do anything in production.

    log            // Logs tasks as they are enqueued and/or run.
}

Use Cases

can-queues is used by CanJS to order task execution. A task is simply the calling of a function, usually a callback function within an event binding.

There are two main reasons tasks are ordered:

  • performance - It can be beneficial to order some tasks to happen at the same time. For example, those that change the DOM. CanJS performs all DOM mutations together in the DOMUI queue. This helps avoid expensive browser layout reflows. Read On Layout & Web Performance for background on why browser layout reflows hurts performance.
  • determinism - Ordering tasks can provide assurances about the state of an application at a particular point in time.

Let's explore the determinism use case a bit more with a small example to shows what a lack of determinism would look like. In the following example, person observable is created, with two observations that derive values from its values:

const person = observe( { name: "Fran", age: 15 } );

const info = new Observation( () => {
    return person.name + " is " + person.age;
} );

const canVote = new Observation( () => {
    return person.age >= 18;
} );

Now let's say we listened to when info and canVote changed and used the other value to print a message:

info.on( function( newInfo ) {
    console.log( "info: " + newInfo + ", canVote:" + canVote.get() );
} );

canVote.on( function( newCanVote ) {
    console.log( "canVote: " + newCanVote + ", info: " + info.get() );
} );

If person.age is set to 19, info and canVote are each updated and their event handlers dispatched. If the updates to info and canVote immediately dispatched their events, you would see something like:

person.age = 19;

// console.log("info: Fran is 19, canVote: false")
// console.log("info: Fran is 19, canVote: true")

Notice that canVote is false. This is because canVote has not been updated yet. CanJS avoids this problem by scheduling callbacks in queues. All "user" events like the ones above (registered with .on()) happen last in the mutateQueue. info and canVote update their values in the deriveQueue. info and canVote are notified of the age change in the notifyQueue queue.

In CanJS, all user event handlers are able to read other values and have those values reflect all prior state changes (mutations).

Use

Use can-queues enqueue and run tasks in one of the following queues:

  1. notifyQueue - Tasks that notify observables "deriving" observables that a source value has changed.
  2. deriveQueue - Tasks that update the value of a "deriving" observable.
  3. domUIQueue - Tasks that update the DOM.
  4. mutateQueue - Tasks that might cause other mutations that add tasks to one of the previous queues.

For example, the following enqueues and runs a console.log("Hello World") in the mutateQueue:

import queues from "can-queues";

queues.batch.start();
queues.mutateQueue.enqueue( console.log, console, [ "say hi" ] );
queues.batch.stop();

A task in can-queues is just:

  • a function (console.log),
  • its this (console),
  • and its arguments ["say hi"]

batch.start begins collecting tasks and batch.stop flushes any enqueued tasks. batch.start and batch.stop can be nested and only fire when the number of queues.batch.start() calls equals the number of queues.batch.stop() calls.

queues.batch.start();
queues.batch.start();
queues.batch.start();
queues.mutateQueue.enqueue( console.log, console, [ "say hi" ] );
queues.batch.stop();
queues.batch.stop();
queues.batch.stop(); //-> logs "say hi"

The enqueueByQueue helper can enqueue multiple tasks and starts and stops a batch. For example, the following will log "running a task" in every queue.

queues.enqueueByQueue( {
    notify: [ console.log ],
    derive: [ console.log ],
    domUI: [ console.log ],
    mutate: [ console.log ]
}, console, [ "running a task" ] );

When enqueuing tasks, to assist with debugging, PLEASE:

  • Give your functions useful names:

//!steal-remove-start Object.defineProperty( this.update, "name", { value: canReflect.getName( this ) + ".update" } );

//!steal-remove-end

- Use the `reasonLog` (described in enqueueByQueue's documentation):
  ```js
queues.notifyQueue.enqueue(
    this.update,
    this,
    [],
    null

    //!steal-remove-start
    /* jshint laxcomma: true */
    , [ canReflect.getName( context ), "changed" ]
    /* jshint laxcomma: false */
    //!steal-remove-end
);

CanJS is much easier to debug if queued tasks can be easily traced to their source in meaningful ways.

Understanding task order

This section describes the order in which tasks run. Tasks run in a particular order within a queue, determined by the type of queue:

  • Basic Queue - Run in first-in-first out.
  • CompletionQueue - Run in first-in-first out, but each task must complete completely before the next task is run.
  • PriorityQueue - Like a CompletionQueue, but run tasks in order of their priority.

The queues themselves run in a particular order. can-queues runs the task queues in the following order:

  1. notifyQueue - Tasks that notify observables "deriving" observables that a source value has changed.
  2. deriveQueue - Tasks that update the value of a "deriving" observable.
  3. domUIQueue - Tasks that update the DOM.
  4. mutateQueue - Tasks that might cause other mutations that add tasks to one of the previous queues.

This means that once the notifyQueue has completed, the deriveQueue's tasks will start and so on. Lets see a brief example where we:

  • Create a person,
  • Derive an info value from it,
  • Update the DOM with the value of info,
  • Listen to changes in age and log the new value.
const person = new observe.Object( { name: "Fran", age: 15 } );

const info = new Observation( function updateInfo() {
    return person.name + " is " + person.age;
} );

const frag = stache( "<h2>{{info}}</h2>" )( { info: info } );
document.body.appendChild( frag );

person.on( "age", function logAgeChanged( newVal ) {
    console.log( "Age changed to ", newVal );
} );

person.age = 22;

When person.age changes, this will:

  1. Enqueue a notify task (onAgeChange) that notifies info that one of its source values is changing.
  2. Enqueue a mutate task (logAgeChanged).
  3. Run info's onAgeChange task. This then enqueue and run a derive task (updateInfo) that will derive the new info value.
  4. info will then enqueue any event handlers listening for changes to its value. stache (via can-view-live) is listening to changes in the domUI queue. can-view-live's setInnerHTML is enqueued.
  5. setInnerHTML is run, updating the page.
  6. The domUI queue is empty, so the mutate task's are run. logAgeChanged is run.

notify then derive then domUI then mutate

NOTE: Tasks in earlier queues will "preempt" tasks in later queues. For example, if a deriveQueue task enqueues a notifyQueue task, the notifyQueue tasks will be flushed before continuing on to additional deriveQueue tasks.

Debugging

can-queues lets you trace the task execution with two different methods:

  • log - log when tasks are enqueued, flushed, or both.
  • logStack - log the tasks that resulted in the current method being run.

Consider the following code that derives an info value from the person observable:

const person = new observe.Object( { name: "Fran", age: 15 } );

const info = new Observation( function updateInfo() {
    return person.name + " is " + person.age;
} );

info.on( function onInfoChanged( newVal ) {
    console.log( "info changed" );
} );

person.age = 22;

If you wanted to know what caused onInfoChanged to run, you could call .logStack(). It would log something similar to the following:

ObserveObject{} set age to 22
NOTIFY ran task: Observation<updateInfo>.onDependencyChange ▶ { ... }
DERIVE ran task: Observation<updateInfo>.update ▶ { ... }
MUTATE ran task: onInfoChanged ▶ { ... }

.logStack() logs each task in the task queue and the reason the initial task was queued. The name of each task and the queue it ran in is logged. You'll also notice that the task object itself is logged (shown as ▶ { ... } above). That object contains references to the following:

{
    fn,      // The function that was run
    context, // The context (`this`) the function was called on
    args,    // The arguments the function was passed
    meta    // Additional information about the task
}

.log() is used to log every task as it is enqueued and flushed. If queues.log() was called prior to person.age being set, the following would be logged:

NOTIFY enqueuing: Observation<updateInfo>.onDependencyChange ▶ { ... }
NOTIFY running  : Observation<updateInfo>.onDependencyChange ▶ { ... }
DERIVE enqueuing: Observation<updateInfo>.update ▶ { ... }
DERIVE running  : Observation<updateInfo>.update ▶ { ... }
MUTATE enqueuing: onInfoChanged ▶ { ... }
MUTATE running  : onInfoChanged ▶ { ... }

Typically, knowing when tasks are enqueued is not helpful for debugging so it's generally more useful to only log when tasks are flushed with:

queues.log( "flush" );

How it works

can-queues works by first creating the queue-state.js module that simply tracks the lastTask that has been executed. Queues update and use this state to provide logStack.

Then, the different queues are created:

  • queue.js
  • completion-queue.js
  • priority-queue.js

can-queues uses those queues to create the notifyQueue, deriveQueue, domUIQueue and mutateQueue, wires them up so when one is done, the next is flushed. It also creates the batch and other methods.

CanJS is part of DoneJS. Created and maintained by the core DoneJS team and Bitovi. Currently 4.3.0.

On this page

Get help

  • Chat with us
  • File an issue
  • Ask questions
  • Read latest news