Timing a specific fetch call

9.3k Views Asked by At

I am making a fetch call like so fetch("foo.com/x.json") and would like to get the time it takes to make the request as it is reported in dev tools.

I have already tried,

performance.mark("fetch-start");
let start = performance.now();
let res = fetch("https://example.com/foo.json", {});
res.then(r => {
  performance.mark("fetch-end");
  performance.measure("fetch", "fetch-start", "fetch-end");
  var measures = performance.getEntriesByName("fetch");
  var measure = measures[0];
  console.log(measure);
});

Have also tried performance.now() -start and they are not as accurate as the devtools, I'm guessing this is due to the fact that the browser does more than one thing at once and doesn't spend all it's time measuring things in isolation.

Is there a way to get as accurate as Developer tools for network timing?

5

There are 5 best solutions below

3
quicVO On

Answer:

Try wrapping the code below in an async function and using performance.now()


Browser JavaScript

async function checkResponseTime(testURL) {
    let time1 = performance.now();
    await fetch(testURL);
    let time2 = performance.now();
    return time2 - time1;
}

(async () => {
    // for the purpose of this snippet example, some host with CORS response
    console.log(await checkResponseTime('https://geoapi.pt'))
})();

Node.JS

let fetch = require('node-fetch');
async function checkResponseTime(testURL) {
    let time1 = performance.now();
    await fetch(testURL);
    let time2 = performance.now();
    return time2 - time1;
}
console.log(await checkResponseTime('https://stackoverflow.com'))
0
Lajos Arpad On

Your question assumes that what you see in DevTools is accurate. It might be a well-founded fact, or an assumption that was not yet proven. I would not try to get close to the values seen in Dev Tools, because however close I may get to Dev Tools' measurements, my results are at best as accurate as Dev Tools' measurements.

So, I would host locally a proxy, that would catch all the requests from the browser to the given site and pass to the server and then get the server's response and send it to the browser. The sole reason for the use of this proxy is to log the time between passing a request received from the browser to the server and the time the response arrives. That would be a very accurate way to monitor times and it would make you intependent of the possible problems of DevTools. In general I do not bother to do this, as the measurements I make are good-enough, but if you want top-notch precision, then I recommend this approach.

4
Khez On

You will never get consistently accurate results in JS land with regards to Dev Tools.

You will sometimes get consistently accurate results in JS land with regards to what a user perceives.

What I mean by this is two fold:

  • DevTools measures network time for fetching a resource
  • JS land measures network time + loading + handoff back to JS when the resource is fully available

A simple experiment can be done:

performance.mark("fetch-start");
const start = performance.now();
const res = fetch("https://example.com/foo.json", {});
res.then(measure).catch(measure);

someHeavyOperation();

function measure(r) {
  performance.mark("fetch-end");
  performance.measure("fetch", "fetch-start", "fetch-end");
  var measures = performance.getEntriesByName("fetch");
  var measure = measures[0];
  console.log(measure);
}

function someHeavyOperation() {
    /* block for 60 seconds! */
    const target = Date.now() + 60*1000;
    for (;;) {
        if (Date.now() > target) {
            break;
        }
    }
}

You can replace someHeavyOperation with many things happening on your computer, including but not limited to:

  • other JS code on the page
  • rendering that is being updated
  • this measurement being in an idle background tab
  • some other process, unrelated to the browser, that's blocking the CPU
  • the browser, loading the result to memory and making it available to JS
  • any combination or all of these together in perfect harmony blocking your measurements

P.S.: You can test the JS handoff time by trying to load a very large (and nested) JSON file. Similar delays should be noticed with JSON.parse on a large string.

0
Zilog80 On

Maybe calling DevTools API via CPD using Node.js and retrieving DevTools timing could be acceptable ?

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto('https://page-to-test')

  const resourceTimingJson = await page.evaluate(() =>
    JSON.stringify(window.performance.getEntriesByType('resource')))

  const resourceTiming = JSON.parse(resourceTimingJson)
  const fetchResourceTiming = resourceTiming.find(element => element.initiatorType.includes('fetch'))

  console.log(fetchResourceTiming)

  await browser.close()
})()

JScript inspired from this link

EDIT: Adding a script focused on the duration :

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()
  await page.goto('https://page-to-test')

  const resourceTimingJson = await page.evaluate(() =>
    JSON.stringify(window.performance.getEntriesByType('resource')))

  const resourceTiming = JSON.parse(resourceTimingJson)
  const fetchResourceTimingAll = resourceTiming.filter(element => element.initiatorType.includes('fetch'));
  if (fetchResourceTimingAll) { fetchResourceTimingAll.forEach( element=>console.log(element.name+" : "+element.duration)) }

  await browser.close()
})()
0
Kaiido On

There is a Resource Timing API being defined that will help us do just this.
Note that this is still a draft and prone to change in the future, but it's already available in the three main browser engines.

const url = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png?" + Math.random();
// create PerformanceObserver
const resourceObserver = new PerformanceObserver( (list) => {
  list.getEntries()
    // get only the one we're interested in
    .filter( ({ name }) => name === url )
    .forEach( (resource) => {
      console.log( resource );
    } );
  // Disconnect after processing the events.
  resourceObserver.disconnect();
} );
// make it a resource observer
resourceObserver.observe( { type: "resource" } );
// start the new request
fetch( url, { mode: "no-cors" } );