What is JSONP and why JSONP exists? Should you ever use it?

What is JSONP and why JSONP exists? Should you ever use it?

What is JSONP and why JSONP exists? Should you ever use it? JSON with Padding — JSONP for short. 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, JSONP was the only option to get a JSON response from a server of a different origin.

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, 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

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.

$.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.

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.

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.

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

The response may look like this:

{
  "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.

<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

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.

https://www.server.com/api/person/1?callback=callbackName
callbackName({
  "firstName": "Maciej",
  "lastName": "Cieslar"
})

Which is the same as:

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.

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

    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.

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:

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


Here’s a live example:

<iframe
     src="https://codesandbox.io/embed/jsonp-zt0ih?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="jsonp"
     allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
     sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
   ></iframe>

## 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`). 

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

javascript json webdev

Bootstrap 5 Complete Course with Examples

Bootstrap 5 Tutorial - Bootstrap 5 Crash Course for Beginners

Nest.JS Tutorial for Beginners

Hello Vue 3: A First Look at Vue 3 and the Composition API

Building a simple Applications with Vue 3

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

JSON Tutorial For Beginners | What is JSON | Learning JSON with JavaScript

JSON Tutorial For Beginners | What is JSON | Learning JSON with JavaScript

Javascript | How To Covert Javascript Array To JSON

In this example, we will see How To Covert Javascript Array To JSON. We can convert Javascript array to json using JSON.stringify() method. At some point in your time, whatever developer you are, you need to deal with JSON data. JSON stands for Javascript Object Notation. Exchange data between client and server is straightforward using JSON.

JavaScript | Convert JSON String to JSON Object

Convert JSON string to JSON object javascript. This tutorial is the purpose to explain the best ways and examples for convert JSON string to a JSON object.

How to use JSON.stringify() and JSON.parse() in JavaScript

JSON.stringify() and JSON.parse() are useful tools for handling JSON-formatted content

JavaScript Tutorial: if-else Statement in JavaScript

This JavaScript tutorial is a step by step guide on JavaScript If Else Statements. Learn how to use If Else in javascript and also JavaScript If Else Statements. if-else Statement in JavaScript. JavaScript's conditional statements: if; if-else; nested-if; if-else-if. These statements allow you to control the flow of your program's execution based upon conditions known only during run time.