Front-end performance and exception reporting

  Front end, javascript

summarize

For background development, logging is a very common development habit, which we usually usetry...catchCode blocks are used to actively capture errors. For each interface call, the time consumption of each interface call is also recorded so that we can monitor the server interface performance and troubleshoot problems.

When I first entered the company, it was in progress.Node.jsWhen I was developing the interface of the, I was not used to logging on the server through the springboard machine every time I checked the problem, and later I got used to this way.

For example:

/**
 * 获取列表数据
 * @parma req, res
 */
exports.getList = async function (req, res) {
    //获取请求参数
    const openId = req.session.userinfo.openId;
    logger.info(`handler getList, user openId is ${openId}`);

    try {
        // 拿到列表数据
        const startTime = new Date().getTime();
        let res = await ListService.getListFromDB(openId);
        logger.info(`handler getList, ListService.getListFromDB cost time ${new Date().getTime() - startDate}`);
        // 对数据处理,返回给前端
        // ...
    } catch(error) {
        logger.error(`handler getList is error, ${JSON.stringify(error)}`);
    }
};

The following code often appears in useNode.jsThe query will be counted in the interfaceDBTime spent, or statisticsRPCTime spent on service invocation to monitor performance bottlenecks and optimize performance; Or for abnormal usetry ... catchActive capture, so that the problem can be traced back and the scene of the problem can be restored at any time.bugThe restoration of.

What about the front end? You can see the following scene.

Recently, during a requirement development, I occasionally found thatwebglIf the rendering of the image fails, or if the image fails to be analyzed, we may not know which image will fail to be analyzed or rendered. Or if there is another requirement recently developed, we will make a report onwebglThe optimization of rendering time and the requirement of image preloading. If there is no performance monitoring, how to calculate the optimization ratio of rendering optimization and image preloading optimization, and how to prove the value of what you have done? It may be through testing students’ black box testing, recording the time before and after optimization, and analyzing how many frames of images have passed from entering the page to the completion of image rendering. Such data may be inaccurate and one-sided, assuming that the test students are not real users and cannot restore the real users’ network environment. Looking back, we found that although our project has made logs and performance statistics at the server level, it has monitored anomalies and made performance statistics at the front end. It is necessary to explore the feasibility of front-end performance and exception reporting.

Exception capture

For the front end, we need nothing more than the following two types of exception capture:

  • Interface calls;
  • Whether the page logic is wrong, for example, the page displays a white screen after the user enters the page;

For interface calls, client-related parameters, such as user OS and browser version, request parameters (e.g. page ID), usually need to be reported at the front end. For the problem of whether the page logic is wrong, besides the user OS and browser version, what is needed is the stack information and the specific error location.

Exception capture method

Global capture

Can be caught by global monitoring exceptions, bywindow.onerrorOr ..addEventListener, look at the following example:

window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
  console.log('errorMessage: ' + errorMessage); // 异常信息
  console.log('scriptURI: ' + scriptURI); // 异常文件路径
  console.log('lineNo: ' + lineNo); // 异常行号
  console.log('columnNo: ' + columnNo); // 异常列号
  console.log('error: ' + error); // 异常堆栈信息
  // ...
  // 异常上报
};
throw new Error('这是一个错误');

viawindow.onerrorEvent, specific exception information, URL of exception file, exception line number and column number, and exception stack information can be obtained, and then the exception is captured and reported to our log server.

Or, throughwindow.addEventListenerMethod to carry out abnormal report, the same reason:

window.addEventListener('error', function() {
  console.log(error);
  // ...
  // 异常上报
});
throw new Error('这是一个错误');

try… catch

Usetry... catchAlthough the exception can be caught well and the page will not hang up due to an error, thetry ... catchThe capture method is too bloated and most code is used.try ... catchPackage, affecting code readability.

faq

Cross-domain scripts cannot accurately capture exceptions

Under normal circumstances, we will put static resources, such asJavaScriptScript to a dedicated static resource server, orCDN, look at the following example:

<!DOCTYPE html>
<html>
<head>
  <title></title>
</head>
<body>
  <script type="text/javascript">
    // 在index.html
    window.onerror = function(errorMessage, scriptURI, lineNo, columnNo, error) {
      console.log('errorMessage: ' + errorMessage); // 异常信息
      console.log('scriptURI: ' + scriptURI); // 异常文件路径
      console.log('lineNo: ' + lineNo); // 异常行号
      console.log('columnNo: ' + columnNo); // 异常列号
      console.log('error: ' + error); // 异常堆栈信息
      // ...
      // 异常上报
    };

  </script>
  <script src="./error.js"></script>
</body>
</html>
// error.js
throw new Error('这是一个错误');

The results show that after cross-domainwindow.onerrorThe correct exception information cannot be captured at all, but one is returned uniformly.Script error,

Solution: YesscriptAdd one labelcrossorigin=”anonymous”And the server addsAccess-Control-Allow-Origin.

<script src="http://cdn.xxx.com/index.js" crossorigin="anonymous"></script>

sourceMap

Usually in the production environment the code is passed throughwebpackAfter packaging, the confusing code is compressed, so we may encounter such problems, as shown in the figure:

We found that all the lines of code reporting errors are in the first line, why? This is because in the production environment, our code is compressed into one line:

!function(e){var n={};function r(o){if(n[o])return n[o].exports;var t=n[o]={i:o,l:!1,exports:{}};return e[o].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=e,r.c=n,r.d=function(e,n,o){r.o(e,n)||Object.defineProperty(e,n,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,n){if(1&n&&(e=r(e)),8&n)return e;if(4&n&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&n&&"string"!=typeof e)for(var t in e)r.d(o,t,function(n){return e[n]}.bind(null,t));return o},r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,"a",n),n},r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.p="",r(r.s=0)}([function(e,n){throw window.onerror=function(e,n,r,o,t){console.log("errorMessage: "+e),console.log("scriptURI: "+n),console.log("lineNo: "+r),console.log("columnNo: "+o),console.log("error: "+t);var l={errorMessage:e||null,scriptURI:n||null,lineNo:r||null,columnNo:o||null,stack:t&&t.stack?t.stack:null};if(XMLHttpRequest){var u=new XMLHttpRequest;u.open("post","/middleware/errorMsg",!0),u.setRequestHeader("Content-Type","application/json"),u.send(JSON.stringify(l))}},new Error("这是一个错误")}]);

I also encountered this problem in my development process. When I was developing a functional component library, I usednpm linkI lost my component library, but because the component library wasnpm linkAfter that, the code under the packaged production environment, all errors are located in the first line.

The solution is to turn it onwebpackThesource-map, we usewebpackThe generated copy after packaging.mapThe script file of the can let the browser track the wrong position. Reference may be made hereinwebpack document.

In fact, it iswebpack.config.jsAdd a line todevtool: 'source-map', as shown below, is an example ofwebpack.config.js

var path = require('path');
module.exports = {
    devtool: 'source-map',
    mode: 'development',
    entry: './client/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'client')
    }
}

InwebpackThe corresponding is generated after packagingsource-map, so that the browser can locate the specific error location:

Opensource-mapThe defect of is compatibility, currently onlyChromeBrowsers andFirefoxBrowsers are the only ones.source-mapSupport. However, we also have solutions to such situations. Introduction can be usednpmLibrary to supportsource-map, you can refer tomozilla/source-map. This ..npmThe library can be run on both the client and the server, but it is more recommended to use it on the server.Node.jsUse when receiving log informationsource-mapResolution, in order to avoid the risk caused by the leakage of source code, as shown in the following code:

const express = require('express');
const fs = require('fs');
const router = express.Router();
const sourceMap = require('source-map');
const path = require('path');
const resolve = file => path.resolve(__dirname, file);
// 定义post接口
router.get('/error/', async function(req, res) {
    // 获取前端传过来的报错对象
    let error = JSON.parse(req.query.error);
    let url = error.scriptURI; // 压缩文件路径
    if (url) {
        let fileUrl = url.slice(url.indexOf('client/')) + '.map'; // map文件路径
        // 解析sourceMap
        let consumer = await new sourceMap.SourceMapConsumer(fs.readFileSync(resolve('../' + fileUrl), 'utf8')); // 返回一个promise对象
        // 解析原始报错数据
        let result = consumer.originalPositionFor({
            line: error.lineNo, // 压缩后的行号
            column: error.columnNo // 压缩后的列号
        });
        console.log(result);
    }
});
module.exports = router;

As shown in the following figure, we can already see that the server has successfully resolved the specific error line number and column number, and we can record it in the form of log to achieve the purpose of front-end anomaly monitoring.

Vue caught exception

In my project, I encountered such problems and usedjs-trackerSuch plug-ins are used to carry out global exception capture and log reporting in a unified way. The result shows that we cannot capture at all.VueThe exception of the component was found inVueThe exception may beVueSelf-givingtry ... catch, will not be passed towindow.onerrorEvent Triggered, How Do We PutVueWhat about the unified capture of exceptions in components?

UseVue.config.errorHandlersuchVueGlobal configuration, available atVueSpecifies the handler that does not capture errors during rendering and observation of the component. When this handler is called, it can get error information andVueExamples.

Vue.config.errorHandler = function (err, vm, info) {
  // handle error
  // `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
  // 只在 2.2.0+ 可用
}

InReact, you can use theErrorBoundaryComponents include business components for exception capture and coordinationReact 16.0+NewcomponentDidCatch API, can achieve unified exception capture and log reporting.

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

The usage is as follows:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

Performance monitoring

The simplest performance monitoring

The most common performance monitoring requirement is that we need to count users from the beginning of page request to allDOMThe time when the rendering of the element is completed, also known as the first screen loading time.DOMThis interface is provided to monitordocumentTheDOMContentLoadedEvents andwindowTheloadEvent statistics page first screen loading time is allDOMRendering time:

<!DOCTYPE html>
<html>
<head>
  <title></title>
  <script type="text/javascript">
    // 记录页面加载开始时间
    var timerStart = Date.now();
  </script>
  <!-- 加载静态资源,如样式资源 -->
</head>
<body>
  <!-- 加载静态JS资源 -->
  <script type="text/javascript">
    document.addEventListener('DOMContentLoaded', function() {
      console.log("DOM 挂载时间: ", Date.now() - timerStart);
      // 性能日志上报
    });
    window.addEventListener('load', function() {
      console.log("所有资源加载完成时间: ", Date.now()-timerStart);
      // 性能日志上报
    });
  </script>
</body>
</html>

For the use of frames, such asVueOr say,React, the component is rendered asynchronously and then mounted to theDOMOf, there were not too many when the page was initializedDOMNodes, please refer to the followingOn the Solution of First Screen Time Acquisition AutomationTo dot the rendering time.

performance

However, the monitoring of the above time is too rough, for example, we want to count the time-consuming and parsing of the network loading of documentsDOMTime-consuming and RenderingDOMTime-consuming, it is not very easy to do, fortunately, the browser provideswindow.performanceInterface, specifically visibleMDN document

Almost all browsers support it.window.performanceInterface, let’s look at printing on the consolewindow.performanceWhat can I get?

As you can see,window,performanceMainly includesmemorynavigationtimingas well astimeOriginAndonresourcetimingbufferfullMethods.

  • navigationObject provides information about operations that occurred within a specified period of time, including whether the page was loaded or refreshed, how many redirections occurred, and so on.
  • timingObject contains delay-related performance information. This is the relevant information mainly reported in our page loading performance optimization requirements.
  • memoryForChromeAdded a non-standard extension, this property provides an object that can get basic memory usage. This should be considered in other browsers.APIThe compatible processing of.
  • timeOriginA high-precision timestamp of the time at which the performance measurement started is returned. As shown in the figure, it is accurate to four decimal places.
  • onresourcetimingbufferfullMethod, it is a method used inresourcetimingbufferfullCalled when the event is triggeredevent handler. This event is triggered when the browser’s resource time performance buffer is full. You can predict the page by listening to this event triggercrash, statistics pagecrashProbability for later performance optimization, as shown in the following example:
function buffer_full(event) {
  console.log("WARNING: Resource Timing Buffer is FULL!");
  performance.setResourceTimingBufferSize(200);
}
function init() {
  // Set a callback if the resource buffer becomes filled
  performance.onresourcetimingbufferfull = buffer_full;
}
<body onload="init()">

Calculate website performance

UseperformanceThetimingProperty, can get the page performance related data, here in many articles have mentioned about the use ofwindow.performance.timingArticles that record page performance, such asalloyteamWritten by the teamPreliminary Study on performance-Monitoring Web Page and Program PerformanceFortimingThe meaning of each attribute of can be understood with the help of the following figure taken from this article. The following code is taken from this article as a tool function reference for calculating website performance:

// 获取 performance 数据
var performance = {  
    // memory 是非标准属性,只在 Chrome 有
    // 财富问题:我有多少内存
    memory: {
        usedJSHeapSize:  16100000, // JS 对象(包括V8引擎内部对象)占用的内存,一定小于 totalJSHeapSize
        totalJSHeapSize: 35100000, // 可使用的内存
        jsHeapSizeLimit: 793000000 // 内存大小限制
    },
 
    //  哲学问题:我从哪里来?
    navigation: {
        redirectCount: 0, // 如果有重定向的话,页面通过几次重定向跳转而来
        type: 0           // 0   即 TYPE_NAVIGATENEXT 正常进入的页面(非刷新、非重定向等)
                          // 1   即 TYPE_RELOAD       通过 window.location.reload() 刷新的页面
                          // 2   即 TYPE_BACK_FORWARD 通过浏览器的前进后退按钮进入的页面(历史记录)
                          // 255 即 TYPE_UNDEFINED    非以上方式进入的页面
    },
 
    timing: {
        // 在同一个浏览器上下文中,前一个网页(与当前页面不一定同域)unload 的时间戳,如果无前一个网页 unload ,则与 fetchStart 值相等
        navigationStart: 1441112691935,
 
        // 前一个网页(与当前页面同域)unload 的时间戳,如果无前一个网页 unload 或者前一个网页与当前页面不同域,则值为 0
        unloadEventStart: 0,
 
        // 和 unloadEventStart 相对应,返回前一个网页 unload 事件绑定的回调函数执行完毕的时间戳
        unloadEventEnd: 0,
 
        // 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0 
        redirectStart: 0,
 
        // 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0 
        redirectEnd: 0,
 
        // 浏览器准备好使用 HTTP 请求抓取文档的时间,这发生在检查本地缓存之前
        fetchStart: 1441112692155,
 
        // DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
        domainLookupStart: 1441112692155,
 
        // DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
        domainLookupEnd: 1441112692155,
 
        // HTTP(TCP) 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等
        // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
        connectStart: 1441112692155,
 
        // HTTP(TCP) 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等
        // 注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间
        // 注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
        connectEnd: 1441112692155,
 
        // HTTPS 连接开始的时间,如果不是安全连接,则值为 0
        secureConnectionStart: 0,
 
        // HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存
        // 连接错误重连时,这里显示的也是新建立连接的时间
        requestStart: 1441112692158,
 
        // HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存
        responseStart: 1441112692686,
 
        // HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存
        responseEnd: 1441112692687,
 
        // 开始解析渲染 DOM 树的时间,此时 Document.readyState 变为 loading,并将抛出 readystatechange 相关事件
        domLoading: 1441112692690,
 
        // 完成解析 DOM 树的时间,Document.readyState 变为 interactive,并将抛出 readystatechange 相关事件
        // 注意只是 DOM 树解析完成,这时候并没有开始加载网页内的资源
        domInteractive: 1441112693093,
 
        // DOM 解析完成后,网页内资源加载开始的时间
        // 在 DOMContentLoaded 事件抛出前发生
        domContentLoadedEventStart: 1441112693093,
 
        // DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
        domContentLoadedEventEnd: 1441112693101,
 
        // DOM 树解析完成,且资源也准备就绪的时间,Document.readyState 变为 complete,并将抛出 readystatechange 相关事件
        domComplete: 1441112693214,
 
        // load 事件发送给文档,也即 load 回调函数开始执行的时间
        // 注意如果没有绑定 load 事件,值为 0
        loadEventStart: 1441112693214,
 
        // load 事件的回调函数执行完毕的时间
        loadEventEnd: 1441112693215
 
        // 字母顺序
        // connectEnd: 1441112692155,
        // connectStart: 1441112692155,
        // domComplete: 1441112693214,
        // domContentLoadedEventEnd: 1441112693101,
        // domContentLoadedEventStart: 1441112693093,
        // domInteractive: 1441112693093,
        // domLoading: 1441112692690,
        // domainLookupEnd: 1441112692155,
        // domainLookupStart: 1441112692155,
        // fetchStart: 1441112692155,
        // loadEventEnd: 1441112693215,
        // loadEventStart: 1441112693214,
        // navigationStart: 1441112691935,
        // redirectEnd: 0,
        // redirectStart: 0,
        // requestStart: 1441112692158,
        // responseEnd: 1441112692687,
        // responseStart: 1441112692686,
        // secureConnectionStart: 0,
        // unloadEventEnd: 0,
        // unloadEventStart: 0
    }
};
// 计算加载时间
function getPerformanceTiming() {
    var performance = window.performance;
    if (!performance) {
        // 当前浏览器不支持
        console.log('你的浏览器不支持 performance 接口');
        return;
    }
    var t = performance.timing;
    var times = {};
    //【重要】页面加载完成的时间
    //【原因】这几乎代表了用户等待页面可用的时间
    times.loadPage = t.loadEventEnd - t.navigationStart;
    //【重要】解析 DOM 树结构的时间
    //【原因】反省下你的 DOM 树嵌套是不是太多了!
    times.domReady = t.domComplete - t.responseEnd;
    //【重要】重定向的时间
    //【原因】拒绝重定向!比如,http://example.com/ 就不该写成 http://example.com
    times.redirect = t.redirectEnd - t.redirectStart;
    //【重要】DNS 查询时间
    //【原因】DNS 预加载做了么?页面内是不是使用了太多不同的域名导致域名查询的时间太长?
    // 可使用 HTML5 Prefetch 预查询 DNS ,见:[HTML5 prefetch](http://segmentfault.com/a/1190000000633364)            
    times.lookupDomain = t.domainLookupEnd - t.domainLookupStart;
    //【重要】读取页面第一个字节的时间
    //【原因】这可以理解为用户拿到你的资源占用的时间,加异地机房了么,加CDN 处理了么?加带宽了么?加 CPU 运算速度了么?
    // TTFB 即 Time To First Byte 的意思
    // 维基百科:https://en.wikipedia.org/wiki/Time_To_First_Byte
    times.ttfb = t.responseStart - t.navigationStart;
    //【重要】内容加载完成的时间
    //【原因】页面内容经过 gzip 压缩了么,静态资源 css/js 等压缩了么?
    times.request = t.responseEnd - t.requestStart;
    //【重要】执行 onload 回调函数的时间
    //【原因】是否太多不必要的操作都放到 onload 回调函数里执行了,考虑过延迟加载、按需加载的策略么?
    times.loadEvent = t.loadEventEnd - t.loadEventStart;
    // DNS 缓存时间
    times.appcache = t.domainLookupStart - t.fetchStart;
    // 卸载页面的时间
    times.unloadEvent = t.unloadEventEnd - t.unloadEventStart;
    // TCP 建立连接完成握手的时间
    times.connect = t.connectEnd - t.connectStart;
    return times;
}

Log submission

Separate log domain name

The purpose of using a separate log domain name for log reporting is to avoid any impact on the business. First, for the server, we certainly do not want to occupy the computing resources of the service server, nor do we want too many logs to pile up on the service server, resulting in insufficient storage space for the service server. Second, we know that in the process of page initialization, the page loading time, PV, UV and other data will be reported, and these reporting requests will be issued almost at the same time as the loading business data, while the browser will generally limit the number of concurrent requests for the same domain name, such asChromeThere will be pairs of concurrent numbers of6The limit of. Therefore, it is necessary to set the domain name separately for the log system to minimize the impact on page loading performance.

Cross-domain issues

For individual log domain names, cross-domain problems will definitely be involved, and the following two solutions are generally adopted:

  • One is structurally emptyImageThe reason is that the request for pictures does not involve cross-domain problems.
var url = 'xxx';
new Image().src = url;
  • utilizeAjaxTo report logs, a cross-domain request header must be opened for the log server interfaceAccess-Control-Allow-Origin:*HereAjaxIt is not mandatory to useGETIf you ask, you can overcome it.URLThe problem of length limitation.
if (XMLHttpRequest) {
  var xhr = new XMLHttpRequest();
  xhr.open('post', 'https://log.xxx.com', true); // 上报给node中间层处理
  xhr.setRequestHeader('Content-Type', 'application/json'); // 设置请求头
  xhr.send(JSON.stringify(errorObj)); // 发送参数
}

The first method used in my project is to construct the emptyImageObject, but we know that forGETThere is a limit on the length of the request, and it is important to ensure that the length of the request does not exceed the threshold.

The response body is omitted

For us to report logs, in fact, for clients, we do not need to consider the results of reporting, and even for reporting failures, we do not need to make any interaction at the front end, so for reporting, we actually useHEADRequest is enough, the interface returns empty results, minimizing the waste of resources caused by reporting logs.

Consolidated reporting

Similar to Sprite diagram, if our application needs to report a large number of logs, it is necessary to consolidate the logs for unified reporting.

The solution can be to attempt to send an asynchronous when the user leaves the page or the component is destroyedPOSTRequest for escalation, but attempt to escalate in uninstall (unload) Document ForwardwebThe server sends data. Ensuring that data is sent during document unloading has always been a difficulty. Because the user agent usually ignores asynchrony generated in the uninstall event handlerXMLHttpRequestBecause it will jump to the next page. So it must be set to synchronous here.XMLHttpRequestRequest?

window.addEventListener('unload', logData, false);

function logData() {
    var client = new XMLHttpRequest();
    client.open("POST", "/log", false); // 第三个参数表明是同步的 xhr
    client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    client.send(analyticsData);
}

The use of synchronization is bound to affect the user experience and even make the user feel that the browser is stuck. As for the product, the experience is very bad. Through consultingMDN document, you can use thesendBeacon()Method will enable the user agent to send data to the server asynchronously when given the opportunity, without delaying the unloading of the page or affecting the loading performance of the next navigation. This solves all the problems when submitting analysis data: make it reliable, asynchronous and will not affect the loading of the next page. In addition, the code is actually simpler than other technologies!

The following example shows a theoretical statistical code pattern-through the use ofsendBeacon()Method sends data to the server.

window.addEventListener('unload', logData, false);

function logData() {
    navigator.sendBeacon("/log", analyticsData);
}

Summary

As a front-end developer, one should keep a reverence for the product, always pursue perfection in performance, and be intolerant of abnormalities. The front-end performance monitoring and exception reporting are particularly important.

There are inevitably problems with the code, which can be used for exceptions.window.onerrorOr ..addEventListenerTo add a global exception capture listener, you may not be able to correctly capture errors in this way: for cross-domain scripts, you need toscriptAdd one labelcrossorigin=”anonymous”; For the code packaged in the production environment, the number of lines generated by the exception cannot be correctly located. You can use thesource-mapTo solve; However, in the case of using the framework, it is necessary to bury the points at the unified anomaly capture points of the framework.

As for the performance monitoring, fortunately the browser has providedwindow.performance APIThrough thisAPI, it is very convenient to get the current page performance-related data.

How can these anomalies and performance data be reported? Generally speaking, in order to avoid the impact on business, log server and log domain name will be established separately, but for different domain names, cross-domain problems will occur. We can do this by constructing an emptyImageObject, or by setting a cross-domain request headerAccess-Control-Allow-Origin:*To solve. In addition, if the reported performance and log data are triggered at high frequency, it can be displayed on the pageunloadWhen unified reporting, andunloadThe asynchronous request at the time of the event may be ignored by the browser and cannot be changed to synchronous request. At this timenavigator.sendBeacon APICan be counted as a great help to us, it can be used to passHTTPAsynchronous Transfer of Small Amount of Data toWebServer. While ignoring the pageunloadThe impact of time.