Working With The Aurelia Task Queue

When it comes to the browser, queues are very important. The concept of a macrotask and microtask exist within the browser which allows standard tasks and micro tasks to be queued up.

The Aurelia Task Queue dependency is not specific functionality to Aurelia itself, but rather wraps native browser methods and provides a fallback if you are not using a supported browser.

The browser itself does not provide direct access to the microtask queue, but leveraging DOM Mutation Observers which use the microtask queue, you can tap into that powerful queue with relative ease (which is what this class does).

There is a good chance you came to this post from a Google search on the Aurelia Task Queue dependency because you want to use it, curious to know what it does or perhaps you were told to use it but not sure how or why you would.

What is it?

The Task Queue dependency allows you to properly batch standard macrotasks and microtasks. As already mentioned it gives you access to an easy-to-use class for queueing standard and microtasks.

Do not be confused, the concept of tasks and microtasks is not an Aurelia concept, it is a concept that exists in the browser and this dependency just ties it all together as well as providing an interface.

Microtasks

A microtask allows you defer a task to be executed on the next event loop or tick. When you queue a microtask you are scheduling for that particular task to execute immediately after the currently executing script.

The microtask queue is used by various web API’s and methods such as MutationObserver, Object.observe (RIP) and Promises. Just like you need to wait your turn at the post office in a queue, microtasks execute in order and are executed at the end of each task. A microtask allows you to write asynchronous code and have it executed in a synchronous way.

One important thing to remember about microtasks is because of the way they operate on the event loop, they always happen before the next task aka macrotask is called.

The awesome thing about microtasks is that before the next standard macrotask is called, all microtasks will be looped and executed in their determined order until the queue is drained. If you have 10 microtasks that modify an element or do something with it, all ten of these tasks will be executed before the next standard task is run.

Also if you queue a microtask from within an existing microtask it gets executed in the current event loop, not the next go-around. As you will see, macrotasks work completely differently in this regard.

The easiest way to describe a microtask is that they get run as soon as possible before rendering.

Macrotasks aka Tasks

A macrotask is completely different to a microtask. It is for turn based asynchronous tasks and the equivalent of using setTimeout or setImmediate. Although limitations surrounding setTimeout and its 4ms delay in most browsers means it is not as efficient.

A macrotask will execute in order and the browser may do rendering stuff between them as they are non-blocking asynchronous operations.

This is why using setTimeout with a value of 0 as a means of making code asynchronous works. Your code inside is executed after the specified length of time and a task is used to call your callback function. Essentially you queue up work to happen on the next frame.

At the end of every task, microtasks are executed. This is commonly referred to as a go-around because of the event loop being used to process tasks and microtasks.

A macrotask will be executed after the next paint (whenever the browser allows that to happen).

Why would you use a task?

In the context of Aurelia, a few parts under the hood in various dependencies use the microtask queue to batch changes in the DOM. As for standard tasks, I have no idea if Aurelia uses those.

For example if you want to focus an element in the page, but it is being modified by some other part of the framework or inside of a repeater, you might run into issues where your code is called before the element is ready in the page or your changes get overwritten.

Using a microtask to make changes to an element in the page for example will push all of your desired changes to the end of the microtask queue.

Your changes are guaranteed to run after all of the Aurelia internal microtasks have been run. This prevents framework specific behaviours or even custom resources (especially in plugins) from overwriting or taking precedence over your changes.

If you are running code that is intensive and potentially blocking, queue a standard task which prevents your code from pausing the execution of any other Javascript thus unblocking your UI.

Working with the Task Queue

There are only a few methods exposed from the Task Queue dependency, and one which you will use in about 97% of all cases: so it is really easy to grok.

Microtasks

Queueing up a microtask is really simple. There is not a lot to do other than call the queueMicroTask method on the task queue class and that’s it.

Calling the queueMicroTask method will push your task to the end of the queue.

import { TaskQueue, inject } from 'aurelia-framework';
@inject(TaskQueue)
export class MyClass {
    constructor(TaskQueue) {        
        this.taskQueue = TaskQueue;    
     }    

     attached() {        
        // A microtask can be queued anywhere, lets put it inside of attached
        this.taskQueue.queueMicroTask(() => {            
            // Do stuff in here        
         });    
      }
} 

Macrotasks

Remember a macrotask is a asynchronous task that does not block the UI, much like you might have used setTimeout for previously.

The code example below is practically the same as above, can you spot the differences?

import { TaskQueue, inject } from 'aurelia-framework';
@inject(TaskQueue)
export class MyClass {
    constructor(TaskQueue) {        
        this.taskQueue = TaskQueue;    
    }    

    attached() {        
        // A macrotask can be queued anywhere, let's put it inside of attached
        this.taskQueue.queueTask(() => {            
            // Do asynchronous stuff in here while any code after this
            // will still run as this doesn't block anything        
        });    
    }
} 

Conclusion

In all honesty, you can get quite deep into how the event loop works when explaining microtasks and macrotasks in Javascript. All you need to know is when to use one or the other.

Use microtasks to batch changes (mutations and reads) to DOM elements. Use macrotasks to wrap potentially long executing or hardware intensive code operations to prevent your application from blocking due to waiting around for your code to finish running before proceeding with the next block of code.

In many cases, using queueTask will achieve the desired effect you are after. The queueMicroTask is handy for situations where you want to perform an action after an attribute has modified an element or a binding has modified something.

2 responses to “Working With The Aurelia Task Queue”

  1. Steve says:

    Dude…..ever take a writing course in college?

Leave a Reply

Your email address will not be published. Required fields are marked *