Categories
JavaScript APIs

Measuring JavaScript App Performance with the Performance API

With the JavaScript Performance API, we have an easy way to measure the performance of a front-end JavaScript apps.

In this article, we’ll look at how to use it to measure our app’s performance.

Performance

We can measure the performance of an app with a few methods. The Performance API provides a precise and consistent definition of time. The API will gives us with a high precision timestamp to mark the time when a piece of code starts running and finishes.

The timestamp is in milliseconds and it should be accurate to 5 microseconds. The browser can represent the value as time in milliseconds accurate to a millisecond if there’re hardware or software constraints that make our browser unable to provide value with the higher accuracy.

We can use it as in the following example:

const startTime = performance.now();
for (let i = 0; i <= 10000; i++) {
  console.log(i);
}
const endTime = performance.now();
console.log(endTime - startTime)

In the code above, we used the performance object to mark the time when the loop starts running and finishes running.

Then we logged the time by subtracting endTime by startTime to give us the elapsed time when the loop ran in milliseconds.

Serializing the Performance object

The performance object is serialized by the toJSON method.

We can use it as follows:

const js = window.performance.toJSON();
console.log(JSON.stringify(js));

Then we get something like:

{"timeOrigin":1579540433373.9158,"timing":{"navigationStart":1579540433373,"unloadEventStart":1579540433688,"unloadEventEnd":1579540433688,"redirectStart":0,"redirectEnd":0,"fetchStart":1579540433374,"domainLookupStart":1579540433376,"domainLookupEnd":1579540433423,"connectStart":1579540433423,"connectEnd":1579540433586,"secureConnectionStart":1579540433504,"requestStart":1579540433586,"responseStart":1579540433678,"responseEnd":1579540433679,"domLoading":1579540433691,"domInteractive":1579540433715,"domContentLoadedEventStart":1579540433715,"domContentLoadedEventEnd":1579540433716,"domComplete":1579540433716,"loadEventStart":1579540433716,"loadEventEnd":0},"navigation":{"type":0,"redirectCount":0}}

logged.

Measuring Multiple Actions

We can use the mark method to mark our actions and the use the measure method to measure the time between actions by passing in the names.

For example, we can measure time with markings as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntriesByName('measureLoop'));

In the code above, we called the mark method before the loop starts and after the loop ends.

Then we call the measure method with a name we create to get the time difference later and both markers so that we can get the time from them and get the time difference.

Then we called performance.getEntriesByName(‘measureLoop’) to get the computed duration with the duration property of the returned object.

‘measureLoop’ is the name we made up to get the time difference by name, and ‘beginLoop' and 'endLoop' are our time markers.

We can get entries marked by the mark method with the getEntriesByType method. It takes a string for the type. To do this, we can write:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntriesByType("mark"))

Then the console.log should get us the following:

[
  {
    "name": "beginLoop",
    "entryType": "mark",
    "startTime": 133.55500000761822,
    "duration": 0
  },
  {
    "name": "endLoop",
    "entryType": "mark",
    "startTime": 1106.3149999827147,
    "duration": 0
  }
]

There’s also a getEntriesByName method which takes the name and the type as the first and second arguments respectively.

For example, we can write:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntriesByName('beginLoop', "mark"));

Then we get:

[
  {
    "name": "beginLoop",
    "entryType": "mark",
    "startTime": 137.6299999828916,
    "duration": 0
  }
]

from the console.log .

We can also use getEntries by passing in an object with the name and entryType properties as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
console.log(performance.getEntries({
  name: "beginLoop",
  entryType: "mark"
}));

Then we get something like:

[
  {
    "name": "[https://fiddle.jshell.net/_display/](https://fiddle.jshell.net/_display/)",
    "entryType": "navigation",
    "startTime": 0,
    "duration": 0,
    "initiatorType": "navigation",
    "nextHopProtocol": "h2",
    "workerStart": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 0.2849999873433262,
    "domainLookupStart": 0.2849999873433262,
    "domainLookupEnd": 0.2849999873433262,
    "connectStart": 0.2849999873433262,
    "connectEnd": 0.2849999873433262,
    "secureConnectionStart": 0.2849999873433262,
    "requestStart": 2.3250000085681677,
    "responseStart": 86.29499998642132,
    "responseEnd": 94.03999999631196,
    "transferSize": 1486,
    "encodedBodySize": 752,
    "decodedBodySize": 1480,
    "serverTiming": [],
    "unloadEventStart": 101.23999998904765,
    "unloadEventEnd": 101.23999998904765,
    "domInteractive": 126.96500000311062,
    "domContentLoadedEventStart": 126.9800000009127,
    "domContentLoadedEventEnd": 127.21500001498498,
    "domComplete": 128.21500000427477,
    "loadEventStart": 128.2249999931082,
    "loadEventEnd": 0,
    "type": "navigate",
    "redirectCount": 0
  },
  {
    "name": "[https://fiddle.jshell.net/js/lib/dummy.js](https://fiddle.jshell.net/js/lib/dummy.js)",
    "entryType": "resource",
    "startTime": 115.49500000546686,
    "duration": 0,
    "initiatorType": "script",
    "nextHopProtocol": "h2",
    "workerStart": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 115.49500000546686,
    "domainLookupStart": 115.49500000546686,
    "domainLookupEnd": 115.49500000546686,
    "connectStart": 115.49500000546686,
    "connectEnd": 115.49500000546686,
    "secureConnectionStart": 0,
    "requestStart": 115.49500000546686,
    "responseStart": 115.49500000546686,
    "responseEnd": 115.49500000546686,
    "transferSize": 0,
    "encodedBodySize": 0,
    "decodedBodySize": 0,
    "serverTiming": []
  },
  {
    "name": "[https://fiddle.jshell.net/css/result-light.css](https://fiddle.jshell.net/css/result-light.css)",
    "entryType": "resource",
    "startTime": 115.77999999281019,
    "duration": 0,
    "initiatorType": "link",
    "nextHopProtocol": "h2",
    "workerStart": 0,
    "redirectStart": 0,
    "redirectEnd": 0,
    "fetchStart": 115.77999999281019,
    "domainLookupStart": 115.77999999281019,
    "domainLookupEnd": 115.77999999281019,
    "connectStart": 115.77999999281019,
    "connectEnd": 115.77999999281019,
    "secureConnectionStart": 0,
    "requestStart": 115.77999999281019,
    "responseStart": 115.77999999281019,
    "responseEnd": 115.77999999281019,
    "transferSize": 0,
    "encodedBodySize": 49,
    "decodedBodySize": 29,
    "serverTiming": []
  },
  {
    "name": "beginLoop",
    "entryType": "mark",
    "startTime": 128.3699999912642,
    "duration": 0
  },
  {
    "name": "measureLoop",
    "entryType": "measure",
    "startTime": 128.3699999912642,
    "duration": 887.0650000171736
  },
  {
    "name": "endLoop",
    "entryType": "mark",
    "startTime": 1015.4350000084378,
    "duration": 0
  }
]

from the console.log .

With markers, we can name our time markers, so we can measure multiple actions.

Clearing Actions

We can clear performance markers by calling the clearMarks method. For example, we can do that as follows:

performance.mark("dog");
performance.mark("dog");
performance.clearMarks('dog');

There’s also a clearMeasures method to clear measurements and clearResourceTimings to clear performance entries.

For example, we can use it as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
performance.clearMeasures("measureLoop");
console.log(performance.getEntriesByName('measureLoop'));

Then we should see an empty array when we call getEntriesByName .

To remove all performance entries, we can use the clearResourceTimings method. It clears the performance data buffer and sets the performance data buffer to zero.

It takes no arguments and we can use it as follows:

performance.mark('beginLoop');
for (let i = 0; i < 10000; i++) {
  console.log(i);
}
performance.mark('endLoop');
performance.measure('measureLoop', 'beginLoop', 'endLoop');
performance.clearResourceTimings();

In the code above, we called the clearResourceTimings method to reset the buffers and performance data to zero so that we can run our performance tests with a clean slate.

Conclusion

We can use the Performance API to measure the performance of a piece of front-end JavaScript code.

To do this, we can use the now method to get the timestamp and then find the difference between the 2.

We can also use the mark method to mark the time and then use the measure method to compute the measurement.

There’re also various ways to get the performance entries and clear the data.

Categories
Testing

How to Test Node.js Apps That Interact With External APIs Using Nock

Today’s web apps rarely live alone. They often make requests to external APIs via HTTP requests.

Fortunately, testing these kinds of code isn’t too hard since we can mock the HTTP responses. This means that our tests don’t have to make the requests in our tests, which is critical for writing repeatable and independent tests.

In this piece, we’ll look at how to test an Express app that interacts with an external API.


Creating a Simple App

First, we create the app that we’ll test. The app will get a random joke from the Chuck Norris Jokes API.

Next, we create an empty project folder and run:

npm init -y

To create an empty package.json file with the default answers.

Next, we install Express and Node-Fetch to create the app and get data from APIs, respectively.

We run:

npm i express node-fetch

To install the packages.

Then we create app.js and add:

To make the app that gets the joke and returns it as the response.


Adding the Test

To add the test, we use the Jest test runner to run the test and check the results, Nock to mock the response for the request that we made in app.js, and Supertest to run the Express app and get the response that we can check.

To do this, we run:

npm i nock supertest jest

Then we create app.test.js for the test code and then add:

In the file.

The code above starts by including our app from app.js and the libraries we use for building the test.

const app = require('./app');  
const nock = require('nock');  
const request = require('supertest');

The first line above imports the Express app instance from app.js. Then the other two lines import the Nock and Supertest libraries, respectively.

Then we add the skeleton for the test by calling test from Jest and pass in the description and async function since we’ll use the promise version of Supertest.

Then using Nock, we mock the response as follows:

As we can see, the mock request is a GET request, which is the same as what we had in app.js. Also, the URL is the same as what we had there.

The difference is that we return a 200 response with the mockResponse object in the test.

Now it doesn’t matter what the actual API returns. Nock will intercept the request and always return the content of mockResponse as the response body for the request made in our route in app.js.

Then we just have to call the route with Supertest as follows:

const res = await request(app).get('/');

To call the route in app.js and then check the response as follows:

expect(res.status).toBe(200);  
expect(res.body).toEqual(mockResponse);

The toEqual method checks for deep equality so we can pass in the whole object to check.

Photo by Filip Mroz on Unsplash.


Running the Test

Now to run our test, we have to add:

"test": "jest --forceExit"

To the scripts section of package.json.

We need the --forceExit option so that the test will exit once the test is run. This is a bug that’s yet to be resolved.

Now we can run the test by running:

npm test

Then we should get:

We should get the same thing no matter how many times we run the test since we mocked the response of the API.

The real API returns something different every time we make a request to it, so we can’t use it for our tests.

Even if we could, it’d be much slower and the API might not always be available.


Conclusion

With Nock, we can mock responses easily for external API requests in our app. Then we can focus on just running our app’s code and checking the results.

Now we have tests that run fast and produce repeatable results that don’t depend on anything external to the tests.

Categories
JavaScript APIs

Using the Browser History API

To navigate back and forth between pages, we can use the History API that comes with most modern browsers.

In this article, we’ll look at how to use it to navigate between pages of the same origin.

Methods in the History API

The History API has a few methods to move back and forth between pages.

There’s the back method to go back to the previous page.

For example, we can use it as follows:

window.history.back()

This is the same as clicking the back button on the browser.

We can go forward by using the forward method as follows:

window.history.forward()

Moving to a Specific Point in History

We can use the go method to load a specific page from session history, identified by its relative position on the current page.

The current page is 0, so a negative integer is the pages before and a positive number is the pages after.

For example:

window.history.go(-1)

is the same as calling back().

and:

window.history.go(1)

is the same as calling forward() .

Calling go with no argument or 0 is the same as refreshing the page.

Photo by Giammarco Boscaro on Unsplash

pushState

The pushState method let us go to the page with the specified URL. It takes 3 arguments. They are:

  • state — this is a Javascript object which is associated with the new history entry created by pushState() . The state object can be anything that can be sanitized. It’s saved onto the user’s disk so that it can be restored when the user restarts the browser. If the state object has a serialized representation that’s bigger than itself, then this method will throw an exception.
  • title — most browsers ignore this string parameter.
  • url — the new history entry’s URL. The browser won’t attempt to load the URL after calling pushState . However, it might load when we restart the browser. The URL can be absolute or relative. The URL must be the same origin as the current URL. Otherwise, an exception will be thrown.

We don’t have to change the URL if we don’t want to. Whatever it is, it must be in the same origin as the current URL.

For example, we can use it as follows:

window.onpopstate = function(event) {
  console.log(
    `location ${document.location} state ${JSON.stringify(event.state)}`
  );
};

window.history.pushState(
  {
    foo: "bar"
  },
  "",
  "/foo"
);

window.history.pushState(
  {
    bar: "baz"
  },
  "",
  "/bar"
);

window.history.back();
window.history.back();
window.history.go(2);
window.history.go(-2);

Then we get:

location https://ib3i4.csb.app/foo state {"foo":"bar"}
location https://ib3i4.csb.app/bar state null
location https://ib3i4.csb.app/bar state {"bar":"baz"}
location https://ib3i4.csb.app/bar state null

from the console.log .

onpopstate is called whenever we navigate between pages.

When we go back, we get the state logged only the first time.

Likewise, when we go forward, we get the state only logged once.

replaceState

The replaceState method is similar to pushState, but it modifies the current history entry instead of adding a new one.

It takes the same arguments as the pushState.

For example, we can use it as follows:

window.onpopstate = function(event) {
  console.log(
    `location ${document.location} state ${JSON.stringify(event.state)}`
  );
};

window.history.pushState(
  {
    bar: "bar"
  },
  "",
  "/foo"
);

window.history.go(0);

window.history.replaceState(
  {
    bar: "baz"
  },
  "",
  "/bar"
);

window.history.go(0);

Then we get:

location https://ib3i4.csb.app/foo state {"bar":"bar"}
location https://ib3i4.csb.app/bar state {"bar":"baz"}

As we can see, the entry of /bar ‘s state went from {“bar”:”bar”} to {“bar”:”baz”} once we refreshed it the second time, so the state was replaced.

Conclusion

We can use the History API by calling the methods in the window.history object.

We can go forward in history with forward, backward with back. To go to any page in history, we can call go with a number.

Also, we can call pushState to push a new entry to the history without going to it immediately. It can load the page on restart or when we call the methods above.

We can use replaceState to replace the current history entry. It takes the same arguments as pushState .

Every page must be of the same origin when using this API.

Categories
JavaScript APIs

Introducing the Page Visibility API

Since most modern browsers are tabbed, it’s possible that a webpage may reside in a tab that stays in the background and not visible to the user.

The Page Visibility API can provide us with insight as to whether a page is visible to the user.

In this article, we’ll look at the Page Visibility API, its use cases, and how to use it.

Visibility Change Event

When a user minimizes the window or switches to another tab, the Page Visibility API sends a visibilitychange event to let listeners know that the state of the page has changed.

We can handle the event when it’s triggered and do something according to the visibility state. For example, we can pause the video when the page is hidden from view.

The visibility state of an iframe is the same as the parent document that the iframe is in. Hiding an iframe with CSS doesn’t trigger visibility events or change the state of the document contained within the iframe .

Use Cases

There’re many use cases for using the API. Some of them include the following:

  • pausing an image carousel when the page is hidden
  • stop polling the server for information when the page is hidden
  • prerendering a page to keep an accurate count of page views
  • switch off the sound when the page is not being viewed

Without the Page Visibility API, developers resort to imperfect solutions like listening to the blur or focus events of the window to help detect whether the page is visible or not.

They don’t tell if they’re hidden or not, just whether they’re in focus or not.

Policies that Help Background Page Performance

Most browsers do a few things to help save resources when a page isn’t in view.

requestAnimationFrame callback won’t be called to improve performance and battery life when a page is in the background.

setTimeout and other timers are throttles in the background or inactive tabs to improve performance.

Throttling is also done in browsers to limit CPU usage by background tabs.

Each background tab has its own time budget between -150ms and 50ms.

Browser windows are subjected to throttling after 30 seconds in Firefox and 10 seconds in Chrome.

Timer tasks are only permitted when the time budget is non-negative.

Once the timer’s code finishes running, the duration it takes to execute is subtracted from the time budget.

The budget regenerates at a rate of 10ms per second in both Firefox and Chrome.

Some processes ate exempt from throttling behavior. Tabs that are playing audio are considered foreground tabs and aren’t subject to throttling.

Code that uses real-time network connections goes unthrottled to prevent the closing of these connections.

IndexedDB processes are also left unthrottled to avoid timeouts.

The Page Visibility API can let us stop these things manually if we want to do so.

Photo by Oscar Sutton on Unsplash

Using the Page Visibility API

The Page Visibility API is part of the document object.

We can use it by checking the document.hidden property or the document.visibilityState property. They’re both read-only.

To watch for changes in both, we can listen to the visibilitychange event.

To do this we can use the following example. Our example will pause a video when we switch to a different tab. First, we add the HTML code for the video as follows:

<video controls src='https://sample-videos.com/video123/mp4/240/big_buck_bunny_240p_30mb.mp4'></video>

Then in our JavaScript code, we can listen to the visibilitychange event as follows:

const video = document.querySelector('video');

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState !== 'visible') {
    video.pause();
  }
})

In our event listener callback, we pause the video when the visibilityState isn’t ‘visible’ , which means the user can’t see the tab either by navigating away from the tab or window, minimizing the window, or turned the screen off.

The alternative to this is to set the event handler to the onvisibilitychange property of document.

document.visibilityState can take on these 3 values:

  • visible — the page is visible to the user as a foreground tab
  • hidden — the page isn’t visible to the user, either because it’s in the background, or the minimized or the device’s screen is off.
  • prerender — the page is being prerendered and isn’t visible to the user. A document may start in this state, but will never switch to this state from any other state since it can only prerender once. Not all browsers support prerendering.
  • unloaded — the page is being unloaded from memory. Not all browsers support this.

Compatibility

This API has been supported for a while. Chrome since version 33 supports this API. Edge, Firefox, IE 10 or later, and Safari 7 or later all support this API. Mobile versions of these browsers also support this API.

The Page Visibility API is useful for detecting the visibility state of the page. We can listen to the visibilitychange event and then get the visibility state with document.visibilityState and so what we want with it.

Categories
JavaScript APIs

Make HTTP Requests with the Fetch API

Before the Fetch API, HTTP requests were made with the XmlHttpRequest object. It’s more difficult to use and it’s not promised based since it was added before promises were built into JavaScript.

Now, we can use the Fetch API to make HTTP requests much more easily.

With it, we have a generic definition of Request and Response objects along with other things for network requests. This allows them to be used whenever they’re needed in the future.

It also provides a definition for related concepts such as CORS and HTTP origin header semantics, replacing existing definitions of them elsewhere.

In this article, we’ll look at how to make client-side HTTP requests with the Fetch API.

Making HTTP Requests

Making HTTP requests starts by using the fetch method. It takes one mandatory argument, which is the path to the resource which we want to fetch.

It returns a promise that resolves to the Response to that request, whether it’s successful or not. We can optionally pass in an init options object as the argument.

Once the Response is retrieved, there’re a number of methods to define what body content is and how it should be handled.

The promise returned by fetch won’t reject HTTP error status even if the response is 404 or 500. It’ll resolve normally with ok status set to false .

fetch won’t receive cross-site cookies. There’s no way to establish a cross-site session using fetch .

fetch won’t send cookies unless we set the credentials init option.

We can call the fetch method as follows:

(async () => {
  const response = await fetch('[https://jsonplaceholder.typicode.com/todos/1'](https://jsonplaceholder.typicode.com/todos/1%27))
  const json = await response.json();
  console.log(json);
})();

The code above gets makes a GET request with fetch and then converts it to a JavaScript object with the json() method of the Response object. Then we can log it in the console.log .

This is the simplest case for an HTTP request. We can also add more options in the second argument. We can set the following options:

  • method — request method
  • headers — request headers that we want to add
  • body — request body. It can be Blob, BufferSource, FormData, URLSearchParams, USVString, or ReadableStream object. GET or HEAD requests can’t have a body.
  • mode — mode for the request. It can becors, no-cors, or same-origin
  • credentials — request credentials we want to use for the requests. Possible values are omit, same-origin, or include . This option must be provided to automatically send cookies for the current domain. Starting with Chrome 50, this property also takes a FederatedCredential instance or a PasswordCredential instance.
  • cache — cache mode we want to use for the request
  • redirect — redirect mode to use. Set this to follow for automatically follows redirects, error to abort with an error if a redirect occurs, or manual to handle redirects manually
  • referrer — a string specifying no-referrer, client, or a URL. Default value is client
  • referrerPolicy — specifies the value of the referrer HTTP header. Can be one of no-referrer, no-referrer-when-downgrade, origin, origin-when-cross-origin, unsafe-url
  • integrity — subresource integrity value of the request
  • keepalive — set this optional to allow the request to outlive the page
  • signal — an AbortSignal object instance to let us communicate with a fetch request and abort it via an AbortController .

For example, we can make a basic POST request by writing:

(async () => {
  const response = await fetch('[https://jsonplaceholder.typicode.com/posts'](https://jsonplaceholder.typicode.com/posts%27), {
    method: 'POST',
    body: JSON.stringify({
      title: 'title',
      body: 'body',
      userId: 1
    }),
    headers: {
      "Content-type": "application/json; charset=UTF-8"
    }
  })
  const json = await response.json();
  console.log(json);
})();

We set all the options in the object of the second argument, including the body and headers.

To upload a file, we can get the file from a file input. Given that we have it in the HTML:

<input type='file' id='file-input'>

Then we can write the following to watch for changes in the value of the file input and upload the file to the server:

const upload = async (file) => {
  const response = await   fetch('[https://localhost/'](https://jsonplaceholder.typicode.com/posts%27), {
    method: 'POST',
    body: file,
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  })
  const json = await response.json();
  console.log(json);
};

const input = document.getElementById('file-input');
input.addEventListener('change', () => {
  upload(input.files[0]);
}, false);

Note that the header may change depending on the server we use. The general idea is that we get the file from the input and then send it in the body of the request.

Manipulating the Response

The Response has a few properties for getting data and manipulating it. We can use the error method to get the error, redirect method creates a new response with a different URL.

The Body object, which the Response object implements have the formData method for reading FormData responses. Likewise, there’s the json method for reading JSON responses and text for reading plain text. They all resolve to promises with the corresponding objects. arrayBuffer method will read binary data and resolves to an ArrayBuffer . blob method reads binary data and resolves it to a Blob .

Value properties that may be useful to us include headers to get the response headers, ok to check if the request was successful, redirect to check whether redirect happened. status is the response status code, statusText has the text that corresponds to the status code. url has the response URL, body has the response body.

The Fetch API is much better than XmlHttpRequest for making HTTP requests. It lets us make most kinds of requests, and it’s promise based so that they can be run sequentially with other promises.

It supports both text and binary bodies. Now we don’t need a third-party HTTP client to make client-side HTTP requests.

Request and Response objects are also standardized so that they can used with other APIs.