JSONP demystified: What it is and why it exists
Tags: javascript, json, webdev
Published: 2019-11-21 14:36:54

---
title: JSONP demystified: What it is and why it exists
published: true
date: 2019-11-21 14:00:51 UTC
tags: javascript,json,webdev
canonical_url: https://blog.logrocket.com/jsonp-demystified-what-it-is-and-why-it-exists/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/n5pn8wlie2rs9bimqwpb.jpeg
---
**Written by [Maciej Cieślar](https://blog.logrocket.com/author/maciejcieslar/)**✏️

JSONP has always been one of the most poorly explained concepts in all of web development. This is likely due to its confusing name and overall sketchy background. Prior to the adoption of the [Cross-Origin Resource Sharing (CORS) standard](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), JSONP was the only option to get a JSON response from a server of a different origin.

After sending a request to a server of a different origin that doesn’t support CORS, the following error would be thrown:

![CORS Support Error](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/cors-support-error.png?resize=730%2C64&ssl=1)

Upon seeing this, many people would Google it just to find out that JSONP would be needed to bypass the same-origin policy. Then jQuery, ubiquitous back in the day, would swoop in with its convenient JSONP implementation baked right into the core library so that we could get it working by switching just one parameter. Many people never understood that what changed completely was the underlying mechanism of sending the request.

```jsx
$.ajax({
 url: 'http://twitter.com/status/user_timeline/padraicb.json?count=10',
 dataType: 'jsonp',
 success: function onSuccess() { }
});
```

In order to understand what went on behind the scenes, let’s take a look at what JSONP really is.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## What is JSONP?

JSON with Padding — JSONP for short — is a technique that allows developers to bypass the same-origin policy enforced by browsers by using the `<script>` element’s nature. The policy disallows reading any responses sent by websites whose origins are different from the one currently used. Incidentally, the policy allows sending a request, but not reading one.

A website’s origin consists of three parts. First, there’s the URI scheme (i.e., `https://`), then the host name (i.e., `logrocket.com`), and, finally, the port (i.e., `443`). Websites like `http://logrocket.com` and `https://logrocket.com` have two different origins due to the URI Scheme difference.

If you wish to learn more about this policy, [look no further](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy).

## How does it work?

Let’s assume that we are on `localhost:8000` and we send a request to a server providing a JSON API.

```jsx
https://www.server.com/api/person/1
```

The response may look like this:

```jsx
{
  "firstName": "Maciej",
  "lastName": "Cieslar"
}
```

But due to the aforementioned policy, the request would be blocked because the origins of the website and the server differ.

Instead of sending the request ourselves, the `<script>` element can be used, to which the policy doesn’t apply — it can load and execute JavaScript from a source of foreign origin. This way, a website located on `https://logrocket.com` can load the Google Maps library from its provider located under a different origin (i.e., CDN).

By providing the API’s endpoint URL to the `<script>`’s `src` attribute, the `<script>` would fetch the response and execute it inside the browser context.

```jsx
<script src="https://www.server.com/api/person/1" async="true"></script>
```

The problem, though, is that the `<script>` element automatically parses and executes the returned code. In this case, the returned code would be the JSON snippet shown above. The JSON would be parsed as JavaScript code and, thus, throw an error because it is not a valid JavaScript.

![Invalid Syntax Error](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2019/11/invalid-syntax-error.png?resize=730%2C362&ssl=1)

A fully working JavaScript code has to be returned for it to be parsed and executed correctly by the `<script>`. The JSON code would work just fine had we assigned it to a variable or passed it as an argument to a function — after all, the JSON format is just a JavaScript object.

So instead of returning a pure JSON response, the server can return a JavaScript code. In the returned code, a function is wrapped around the JSON object. The function name has to be passed by the client since the code is going to be executed in the browser. The function name is provided in the query parameter called `callback`.

After providing the callback’s name in the query, we create a function in the global (`window`) context, which will be called once the response is parsed and executed.

```jsx
https://www.server.com/api/person/1?callback=callbackName
```
```jsx
callbackName({
  "firstName": "Maciej",
  "lastName": "Cieslar"
})
```
Which is the same as:

```jsx
window.callbackName({
  "firstName": "Maciej",
  "lastName": "Cieslar"
})
```

The code is executed in the browser’s context. The function will be executed from inside the code downloaded in `<script>` in the global scope.

In order for JSONP to work, both the client and the server have to support it. While there’s no standard name for the parameter that defines the name of the function, the client will usually send it in the query parameter named `callback`.

## Implementation

Let’s create a function called `jsonp` that will send the request in the JSONP fashion.

```jsx
let jsonpID = 0;

function jsonp(url, timeout = 7500) {
  const head = document.querySelector('head');
  jsonpID += 1;

  return new Promise((resolve, reject) => {
    let script = document.createElement('script');
    const callbackName = `jsonpCallback${jsonpID}`;

    script.src = encodeURI(`${url}?callback=${callbackName}`);
    script.async = true;

    const timeoutId = window.setTimeout(() => {
      cleanUp();

      return reject(new Error('Timeout'));
    }, timeout);

    window[callbackName] = data => {
      cleanUp();

      return resolve(data);
    };

    script.addEventListener('error', error => {
      cleanUp();

      return reject(error);
    });

    function cleanUp() {
      window[callbackName] = undefined;
      head.removeChild(script);
      window.clearTimeout(timeoutId);
      script = null;
    }


    head.appendChild(script);
  });
}
```

As you can see, there’s a shared variable called `jsonpID` — it will be used to make sure that each request has its own unique function name.

First, we save the reference to the `<head>` object inside a variable called `head`. Then we increment the `jsonpID` to make sure the function name is unique. Inside the callback provided to the returned promise, we create a `<script>` element and the `callbackName` consisting of the string `jsonpCallback` concatenated with the unique ID.

Then, we set the `src` attribute of the `<script>` element to the provided URL. Inside the query, we set the callback parameter to equal `callbackName`. Note that this simplified implementation doesn’t support URLs that have predefined query parameters, so it wouldn’t work for something like `https://logrocket.com/?param=true`, because we would append `?` at the end once again.

We also set the `async` attribute to `true` in order for the script to be non-blocking.

There are three possible outcomes of the request:

1. The request is successful and, hopefully, executes the `window[callbackName]`, which resolves the promise with the result (JSON)
2. The `<script>` element throws an error and we reject the promise
3. The request takes longer than expected and the timeout callback kicks in, throwing a timeout error

```jsx
const timeoutId = window.setTimeout(() => {
  cleanUp();

  return reject(new Error('Timeout'));
}, timeout);

window[callbackName] = data => {
  cleanUp();

  return resolve(data);
};

script.addEventListener('error', error => {
  cleanUp();

  return reject(error);
});
```

The callback has to be registered on the `window` object for it to be available from inside the created `<script>` context. Executing a function called `callback()` in the global scope is equivalent to calling `window.callback()`.

By abstracting the cleanup process in the `cleanUp` function, the three callbacks — timeout, success, and error listener — look exactly the same. The only difference is whether they resolve or reject the promise.

```jsx
function cleanUp() {
  window[callbackName] = undefined;
  head.removeChild(script);
  window.clearTimeout(timeoutId);
  script = null;
}
```

The `cleanUp` function is an abstraction of what needs to be done in order to clean up after the request. The function first removes the callback registered on the window, which is called upon successful response. Then it removes the `<script>` element from `<head>` and clears the timeout. Also, just to be sure, it sets the `script` reference to `null` so that it is garbage-collected.

Finally, we append the `<script>` element to `<head>` in order to fire the request. `<script>` will send the request automatically once it is appended.

Here’s the example of the usage:

```jsx
jsonp('https://gist.github.com/maciejcieslar/1c1f79d5778af4c2ee17927de769cea3.json')
 .then(console.log)
 .catch(console.error);
```

Here’s a [live example](https://codesandbox.io/embed/festive-mendel-zt0ih?fontsize=14&hidenavigation=1&theme=dark).

## Summary

By understanding the underlying mechanism of JSONP, you probably won’t gain much in terms of directly applicable web skills, but it’s always interesting to see how people’s ingenuity can bypass even the strictest policies.

JSONP is a relic of the past and shouldn’t be used due to numerous limitations (e.g., being able to send GET requests only) and many security concerns (e.g., the server can respond with whatever JavaScript code it wants — not necessarily the one we expect — which then has access to everything in the context of the window, including `localStorage` and `cookies`). [Read more here](https://stackoverflow.com/questions/613962/is-jsonp-safe-to-use).

Instead, we should rely on the CORS mechanism to provide safe cross-origin requests.

* * * 
**Editor's note:** Seeing something wrong with this post? You can find the correct version [here](https://blog.logrocket.com/jsonp-demystified-what-it-is-and-why-it-exists/).

## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *
The post [JSONP demystified: What it is and why it exists](https://blog.logrocket.com/jsonp-demystified-what-it-is-and-why-it-exists/) appeared first on [LogRocket Blog](https://blog.logrocket.com).