You’ve probably heard that Angular uses zone.js, but why does Angular use zone.js, and what does it offer? Today we will write a separate article about zone.js, and its role in the Angular framework will be covered in the next article.

What is a Zone? The official documentation explains that a Zone is an execution context that spans multiple asynchronous tasks. To sum up, Zone has a particularly powerful ability to intercept or track asynchronous tasks. Let’s take a look at an example to demonstrate its capabilities and briefly examine how it works behind the scenes.

<button id="b1">Bind Error</button>
<button id="b2">Cause Error</button>
<script>
  function main() {
    b1.addEventListener('click', bindSecondButton);
  }
  function bindSecondButton() {
    b2.addEventListener('click', throwError);
  }
  function throwError() {
    throw new Error('aw shucks');
  }
  main();
</script>
Copy the code

This is a simple HTML page. Page loading adds a click event to the first button, its click event function adds a click event to the second button, and its click event function throws an exception. We click the first button and the second button in turn, and the console displays as follows:

(index) : 26 Uncaught Error: aw shucks. At HTMLButtonElement throwError (index) : (so)Copy the code

But how would the console output be different if we started the run code with zone.js? Let’s adjust the start code first:

  Zone.current.fork(
      {
        name: 'error',
        onHandleError: function (parentZoneDelegate, currentZone, targetZone, error) {
          console.log(error.stack);
        }
      }
    ).fork(Zone.longStackTraceZoneSpec).run(main);
Copy the code

The console output is as follows:

Error: Aw shucks at HTMLButtonElement. ThrowError (index) : (so) at ZoneDelegate. InvokeTask (31) zone. Js: 406: at zone. RunTask (zone.js:178:47) at ZoneTask.invokeTask [as invoke] (zone.js:487:34) at invokeTask (zone.js:1600:14) at HTMLButtonElement.globalZoneAwareCallback (zone.js:1626:17) at ____________________Elapsed_571_ms__At__Mon_Jan_31_2022_20_09_09_GMT_0800_________ (localhost) at Object.onScheduleTask (long-stack-trace-zone.js:105:22) at ZoneDelegate.scheduleTask (zone.js:386:51) at Zone.scheduleTask (zone.js:221:43) at  Zone.scheduleEventTask (zone.js:247:25) at HTMLButtonElement.addEventListener (zone.js:1907:35) at HTMLButtonElement. BindSecondButton ((index) : masters) at ZoneDelegate. InvokeTask (31) zone. Js: 406: at zone. RunTask (zone.js:178:47) at ____________________Elapsed_2508_ms__At__Mon_Jan_31_2022_20_09_06_GMT_0800_________ (localhost) at Object.onScheduleTask (long-stack-trace-zone.js:105:22) at ZoneDelegate.scheduleTask (zone.js:386:51) at Zone.scheduleTask (zone.js:221:43) at Zone.scheduleEventTask (zone.js:247:25) at HTMLButtonElement.addEventListener Zone.js :1907:35 at main (index) :20:10 at Zonedelegate. invoke (zone.js:372:26) at Zone.run (zone.js:134:43)Copy the code

By comparison, we know that without introducing zone.js, we only know through the error call stack that exceptions are thrown by button 2’s click function. With zone.js, we know not only that exceptions are thrown by button 2’s click function, but that its click function is bound by button 1’s click function, and even that the application is initially launched by main. This ability to continuously track multiple asynchronous tasks is extremely important in large and complex projects, so let’s see how zone.js does it.

Zone.js takes over asynchronous apis provided by browsers, such as click events, timers, and so on. Because of this, it can have stronger control over the intervention of asynchronous operations, providing more capabilities. Now let’s take a click event and see how it works.

proto[ADD_EVENT_LISTENER] = makeAddListener(nativeAddEventListener,..)
Copy the code

In the above code, proto refers to eventTarget. prototype, which means this line of code redefines the addEventListener function. Let’s go ahead and look at what the makeAddListener function does.

function makeAddListener() { ...... NativeListener. Apply (this, arguments); . Const task = zone.scheduleEventTask(source,...) . }Copy the code

This function does two main things, one is to execute the browser provided addEventListener function in the custom function, and the other is to schedule an event task for each click function, which is an important factor of zone.js’s powerful intervention into the asynchronous API.

Now let’s go back to the example at the beginning of this article to see how the console can output a complete complete function call stack. We just looked at the makeAddListener function, which schedules an event task for each click function, the execution of the zone.scheduleEventTask function. This scheduling event task function actually ends up executing onScheduleTask:

onScheduleTask: function (... , task) { const currentTask = Zone.currentTask; let trace = currentTask && currentTask.data && currentTask.data[creationTrace] || []; trace = [new LongStackTrace()].concat(trace); task.data[creationTrace] = trace; }Copy the code

The complete stack of function calls that the console outputs at the beginning of this article is stored in currenttask.data [creationTrace], which is an array of LongStackTrace instances. Each time an asynchronous task occurs, the onScheduleTask function keeps track of the current stack of function calls, as can be seen in the constructor of class LongStackTrace:

class LongStackTrace {
    constructor() {
        this.error = getStacktrace();
        this.timestamp = new Date();
    }
}
function getStacktraceWithUncaughtError() {
    return new Error(ERROR_TAG);
}
Copy the code

Enclosing the error storage is the function call stack, getStacktrace function is usually called getStacktraceWithUncaughtError function, we see the new error will be able to know about the call stack is how.

This article examines only a sample of zone.js capabilities, but you can refer to the official documentation if you want to learn more. This example gives you a sense of what zone.js is, as it is also an essential cornerstone of Angular change detection. I’ll cover this in the next article. Welcome to follow my personal wechat public account [Zhu Yujie’s blog], and we will share more front-end knowledge in the future.