Как печатать путь к текущей зоне в zone.js

Я экспериментирую с зонами (zone.js), и я понял, что не знаю, какой лучший способ распечатать все зоны от корня до текущей зоны, в которой была ошибка.

Например, этот код использует два вложенных обратных вызова с setTimeout(), а затем вызывает функцию с именем failedFunc(), которая вызывает ошибку:

require('zone.js');

function failedFunc() {
  throw new Error("it broken");
}

let rootZone = Zone.current;

function func1() {
  let zoneA = rootZone.fork({name: 'zoneA'});
  zoneA.run(() => {
    setTimeout(() => func2());
  });
}

function func2() {
  let zoneB = Zone.current.fork({name: 'zoneB'});

  zoneB.run(() => {
    setTimeout(() => failedFunc());
  });
}

func1();

Когда я запускаю этот пример, он дает следующий вывод:

/.../zone-test/node_modules/zone.js/dist/zone-node.js:170
                        throw error;
                        ^
Error: it broken
    at new Error (native)
    at failedFunc (/.../zone-test/zone_02.js:12:9) [zoneB]
    at Timeout.setTimeout (/.../zone-test/zone_02.js:28:22) [zoneB]
    at Zone.runTask (/.../zone-test/node_modules/zone.js/dist/zone-node.js:166:47) [<root> => zoneB]
    at Timeout.ZoneTask.invoke (/.../zone-test/node_modules/zone.js/dist/zone-node.js:416:38) [<root>]
    at Timeout.data.args.(anonymous function) [as _onTimeout] (/.../zone-test/node_modules/zone.js/dist/zone-node.js:1590:25) [<root>]
    at ontimeout (timers.js:365:14) [<root>]
    at tryOnTimeout (timers.js:237:5) [<root>]
    at Timer.listOnTimeout (timers.js:207:5) [<root>]

Правильный путь для зоны, которая выбрасывает ошибку, <root> = > zoneA = > zoneB.

Однако это не очевидно из результата. Там просто [root] и zoneB, и он не упоминает zoneA, который запутывает. Я думаю, это связано с тем, что патч zone.js monkey добавляет информацию о зоне в строки в стеке вызовов. Поэтому, когда я использую setTimeout(), чем создание zoneA не соответствует какой-либо строке на выходе, и поэтому я не вижу его нигде.

Тем не менее, я могу распечатать путь к текущей зоне, итерации всех его родителей, но это означает, что мне нужно знать, где произошла ошибка, и добавить к нему следующий код (что было бы очень утомительно на практике):

function failedFunc() {
  let zone = Zone.current;
  let zones = [];
  while (zone) {
    zones.push(zone.name);
    zone = zone.parent;
  }
  console.log(zones);

  throw new Error("it broken");
}

// ...

Теперь, когда я запустил это, я получу то, что мне нужно:

[ 'zoneB', 'zoneA', '<root>' ]

/.../zone-test/node_modules/zone.js/dist/zone-node.js:170
                        throw error;
                        ^
Error: it broken
    at new Error (native)

Итак, мне интересно, как использовать zone.js на практике и таким образом, чтобы это было проще, чем это.

Ответ 1

Чтобы получить полный стек из zone, вы должны использовать long-stack-trace-zone

Обратите внимание, что я выбрасываю исключение, поэтому вы увидите ошибку в моем фрагменте. Вопрос и ответ о печати полного stacktrace:

function failedFunc() {
  throw new Error("it broken");
}

function func1() {
  let zoneA = Zone.current.fork({
    name: 'zoneA'
  });
  zoneA.run(function() {
    setTimeout(function() {
      func2()
    });
  });
}

function func2() {
  let zoneB = Zone.current.fork({
    name: 'zoneB'
  });

  zoneB.run(function() {
    setTimeout(function() {
      failedFunc()
    });
  });
}

Zone.current.fork({
  onHandleError: function(parentZoneDelegate, currentZone, targetZone, error) {
    console.log(error.stack);
  }
}).fork(Zone.longStackTraceZoneSpec).run(func1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.8.5/zone.min.js"></script>
<!--  https://raw.githubusercontent.com/angular/zone.js/master/dist/long-stack-trace-zone.min.js -->
<script>
!function(t,a){"object"==typeof exports&&"undefined"!=typeof module?a():"function"==typeof define&&define.amd?define(a):a()}(this,function(){"use strict";function t(){return new Error(u)}function a(){try{throw t()}catch(a){return a}}function e(t){return t.stack?t.stack.split(i):[]}function n(t,a){for(var n=e(a),r=0;r<n.length;r++){var c=n[r];s.hasOwnProperty(c)||t.push(n[r])}}function r(t,a){var e=[a.trim()];if(t)for(var r=(new Date).getTime(),c=0;c<t.length;c++){var o=t[c],s=o.timestamp,f="____________________Elapsed "+(r-s.getTime())+" ms; At: "+s;f=f.replace(/[^\w\d]/g,"_"),e.push(h.replace(_,f)),n(e,o.error),r=s.getTime()}return e.join(i)}function c(t,a){a>0&&(t.push(e((new l).error)),c(t,a-1))}function o(){var t=[];c(t,2);for(var a=t[0],e=t[1],n=0;n<a.length;n++){var r=a[n];if(r.indexOf(u)==-1){var o=r.match(/^\s*at\s+/);if(o){h=o[0]+_+" (http://localhost)";break}}}for(var n=0;n<a.length;n++){var r=a[n],i=e[n];if(r!==i)break;s[r]=!0}}var i="\n",s={},f="__creationTrace__",u="STACKTRACE TRACKING",_="__SEP_TAG__",h=_+"@[native]",l=function(){function t(){this.error=g(),this.timestamp=new Date}return t}(),d=t(),k=a(),g=d.stack?t:k.stack?a:t;Zone.longStackTraceZoneSpec={name:"long-stack-trace",longStackTraceLimit:10,getLongStackTrace:function(t){if(t){var a=t[Zone.__symbol__("currentTask")],e=a&&a.data&&a.data[f];return e?r(e,t.stack):t.stack}},onScheduleTask:function(t,a,e,n){var r=Zone.currentTask,c=r&&r.data&&r.data[f]||[];return c=[new l].concat(c),c.length>this.longStackTraceLimit&&(c.length=this.longStackTraceLimit),n.data||(n.data={}),n.data[f]=c,t.scheduleTask(e,n)},onHandleError:function(t,a,e,n){var c=Zone.currentTask||n.task;if(n instanceof Error&&c){var o=r(c.data&&c.data[f],n.stack);try{n.stack=n.longStack=o}catch(i){}}return t.handleError(e,n)}},o()});
</script>