Skip to content

Testing asynchronous code with fakeAsync. Microtasks, Macrotasks.

Nikolay Alipiev edited this page Apr 10, 2020 · 2 revisions

Resource: https://www.joshmorony.com/testing-asynchronous-code-with-fakeasync-in-angular/

FakeAsync, flushMicrotasks, and tick

Zone.js is included in Angular and patches the browser so that we can detect when asynchronous code like Promises and setTimeout complete. This is used by Angular to trigger its change detection, which is a weird, complex, and wonderful process I tried to cover somewhat in this article. It's important that Angular knows when asynchronous operations complete, because perhaps one of those operations might change some property binding that now needs to be reflected in a template.

So, the zone that Angular uses allows it to detect when asynchronous functions complete. FakeAsync is similar in concept, except that it kind of "catches" any asynchronous operations. Any asynchronous code that is triggered is added to an array, but it is never executed… until we tell it do so. Our tests can then wait until those operations have completed before we make our assertions.

When a test is running within a fakeAsync zone , we can use two functions called flushMicrotasks and tick. The tick function will advance time by a specified number of milliseconds, so tick(100) would execute any asynchronous tasks that would occur within 100ms. The flushMicrotasks function will clear any microtasks that are currently in the queue.

In short, though, a microtask is created when we perform asynchronous tasks like setting up a callback for a promise. However, not all asynchronous code is added as microtasks, some things like setTimeout are added as normal tasks or macrotasks. This is an important difference because flushMicrotasks will not execute timers like setTimeout.


Difference between microtask and macrotask within an event loop context

One go-around of the event loop will have exactly one task being processed from the macrotask queue (this queue is simply called the task queue in the WHATWG specification). After this macrotask has finished, all available microtasks will be processed, namely within the same go-around cycle. While these microtasks are processed, they can queue even more microtasks, which will all be run one by one, until the microtask queue is exhausted.

What are the practical consequences of this?

If a microtask recursively queues other microtasks, it might take a long time until the next macrotask is processed. This means, you could end up with a blocked UI, or some finished I/O idling in your application.

However, at least concerning Node.js's process.nextTick function (which queues microtasks ), there is an inbuilt protection against such blocking by means of process.maxTickDepth. This value is set to a default of 1000, cutting down further processing of microtasks after this limit is reached which allows the next macrotask to be processed)

So when to use what?

Basically, use microtasks when you need to do stuff asynchronously in a synchronous way (i.e. when you would say perform this (micro-)task in the most immediate future). Otherwise, stick to macrotasks.

Examples

macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering microtasks: process.nextTick, Promises, Object.observe, MutationObserver

Basic concept

  • An event loop has one or more task queues.(task queue is macrotask queue)
  • Each event loop has a microtask queue.
  • task queue = macrotask queue != microtask queue
  • a task may be pushed into macrotask queue, or microtask queue
  • when a task is pushed into a queue(micro/macro),we mean preparing work is finished, so the task can be executed now.

… and the event loop simplified process model:

  1. run the oldest task in macrotask queue, then remove it.

  2. run all available tasks in microtask queue, then remove them.

  3. next round: run next task in macrotask queue (jump to step 2);# #

Clone this wiki locally