¿Cómo hacer una solicitud JSONP desde Javascript sin JQuery?

122

¿Puedo realizar una solicitud JSONP entre dominios en JavaScript sin usar jQuery u otra biblioteca externa? Me gustaría usar JavaScript y luego analizar los datos y convertirlos en un objeto para poder usarlo. ¿Tengo que usar una biblioteca externa? Si no es así, ¿cómo puedo hacerlo?

Dave
fuente

Respuestas:

151
function foo(data)
{
    // do stuff with JSON
}

var script = document.createElement('script');
script.src = '//example.com/path/to/jsonp?callback=foo'

document.getElementsByTagName('head')[0].appendChild(script);
// or document.head.appendChild(script) in modern browsers
Matt Ball
fuente
2
Aquí hay un JSBin que se puede usar para jugar con JSONP de Wikipedia. Se hizo referencia en esta respuesta .
rkagerer
1
Creo que vale la pena señalar que la respuesta debe ser de la forma:, foo(payload_of_json_data)la idea es que cuando se carga en la etiqueta del script, llama a la función foo con la carga útil ya como un objeto javascript y no es necesario analizar.
Pulpo
@WillMunn No creo que eso sea factible con JSONP. Es un truco de los días anteriores a CORS. ¿Para qué necesitas establecer encabezados? El servidor necesita específicamente aceptar solicitudes JSONP, por lo que debe configurarse para que sirva de manera sensata.
Matt Ball
tienes razón, leí mal algo de la documentación de la API, hay un parámetro de consulta especial para hacer lo que quería al usar las aplicaciones jsonp.
Will Munn
@WillMunn no se preocupe. ¡Me alegro de que hayas podido solucionarlo!
Matt Ball
37

Ejemplo ligero (con soporte para onSuccess y onTimeout). Debe pasar el nombre de devolución de llamada dentro de la URL si lo necesita.

var $jsonp = (function(){
  var that = {};

  that.send = function(src, options) {
    var callback_name = options.callbackName || 'callback',
      on_success = options.onSuccess || function(){},
      on_timeout = options.onTimeout || function(){},
      timeout = options.timeout || 10; // sec

    var timeout_trigger = window.setTimeout(function(){
      window[callback_name] = function(){};
      on_timeout();
    }, timeout * 1000);

    window[callback_name] = function(data){
      window.clearTimeout(timeout_trigger);
      on_success(data);
    }

    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.src = src;

    document.getElementsByTagName('head')[0].appendChild(script);
  }

  return that;
})();

Uso de muestra:

$jsonp.send('some_url?callback=handleStuff', {
    callbackName: 'handleStuff',
    onSuccess: function(json){
        console.log('success!', json);
    },
    onTimeout: function(){
        console.log('timeout!');
    },
    timeout: 5
});

En GitHub: https://github.com/sobstel/jsonp.js/blob/master/jsonp.js

sollozo
fuente
29

¿Qué es JSONP?

Lo importante para recordar con jsonp es que en realidad no es un protocolo o tipo de datos. Es solo una forma de cargar un script sobre la marcha y procesar el script que se introduce en la página. En el espíritu de JSONP, esto significa introducir un nuevo objeto javascript desde el servidor en la aplicación / script del cliente.

¿Cuándo se necesita JSONP?

Es un método para permitir que un dominio acceda / procese datos de otro en la misma página de forma asincrónica. Principalmente, se utiliza para anular las restricciones de CORS (intercambio de recursos de origen cruzado) que se producirían con una solicitud XHR (ajax). Las cargas de scripts no están sujetas a las restricciones de CORS.

Como se hace

La introducción de un nuevo objeto javascript desde el servidor se puede implementar de muchas maneras, pero la práctica más común es que el servidor implemente la ejecución de una función de 'devolución de llamada', con el objeto requerido pasado a ella. La función de devolución de llamada es solo una función que ya ha configurado en el cliente a la que llama el script que carga en el punto en el que carga el script para procesar los datos que se le pasan.

Ejemplo:

Tengo una aplicación que registra todos los elementos en la casa de alguien. Mi aplicación está configurada y ahora quiero recuperar todos los elementos del dormitorio principal.

Mi aplicación está encendida app.home.com. Las apis de las que necesito cargar datos están activadas api.home.com.

A menos que el servidor esté configurado explícitamente para permitirlo, no puedo usar ajax para cargar estos datos, ya que incluso las páginas en subdominios separados están sujetas a restricciones XHR CORS.

Idealmente, configure las cosas para permitir XHR de dominio x

Idealmente, dado que la api y la aplicación están en el mismo dominio, podría tener acceso para configurar los encabezados api.home.com. Si lo hago, puedo agregar un Access-Control-Allow-Origin: elemento de encabezado que otorgue acceso a app.home.com. Suponiendo que el encabezado está configurado de la siguiente manera:, Access-Control-Allow-Origin: "http://app.home.com"esto es mucho más seguro que configurar JSONP. Esto se debe a que app.home.compuede obtener todo lo que desea api.home.comsin api.home.comdar acceso a CORS a todo Internet.

La solución XHR anterior no es posible. Configurar JSONP en mi script de cliente: configuro una función para procesar la respuesta desde el servidor cuando hago la llamada JSONP. :

function processJSONPResponse(data) {
    var dataFromServer = data;
}

El servidor deberá configurarse para devolver un mini script con un aspecto similar. "processJSONPResponse('{"room":"main bedroom","items":["bed","chest of drawers"]}');"Podría estar diseñado para devolver una cadena de este tipo si //api.home.com?getdata=room&room=main_bedroomse llama a algo como .

Luego, el cliente configura una etiqueta de secuencia de comandos como tal:

var script = document.createElement('script');
script.src = '//api.home.com?getdata=room&room=main_bedroom';

document.querySelector('head').appendChild(script);

Esto carga el script e inmediatamente llama window.processJSONPResponse()como escrito / echo / impreso por el servidor. Los datos pasados ​​como parámetro a la función ahora se almacenan en la dataFromServervariable local y puede hacer con ellos lo que necesite.

Limpiar

Una vez que el cliente tiene los datos, es decir. Inmediatamente después de que se agrega el script al DOM, el elemento del script se puede eliminar del DOM:

script.parentNode.removeChild(script);
rocío
fuente
2
Muchas gracias, esto me ayudó mucho con mi proyecto. Un problema menor: lo he recibido SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data. Después de agregar comillas simples a los datos, todo funcionó bien, así que:"processJSONPResponse('{"room":"main bedroom","items":["bed","chest of drawers"]}');"
Hein van Dyke
17

Tengo entendido que en realidad usas etiquetas de script con JSONP, así que ...

El primer paso es crear su función que manejará el JSON:

function hooray(json) {
    // dealin wit teh jsonz
}

Asegúrese de que esta función sea accesible a nivel global.

A continuación, agregue un elemento de secuencia de comandos al DOM:

var script = document.createElement('script');
script.src = 'http://domain.com/?function=hooray';
document.body.appendChild(script);

El script cargará el JavaScript que construye el proveedor de API y lo ejecutará.

sdleihssirhc
fuente
2
Gracias a todos. Lo tengo disparado a través de Internet en busca de algunos datos y luego hago algo con él. Usé eval () en los datos de respuesta que me ayudaron a lidiar con el objeto - matriz que es (creo). {Era un oso calculando el análisis con mi limitada capacidad intelectual, pero finalmente obtuve el valor obtenido}. Fantástico.
Dave
@Dave @ Matt Tal vez yo soy difusa en JSONP, pero que no es necesario evalo parseni nada. Debería obtener JavaScript que el navegador pueda ejecutar, ¿verdad?
sdleihssirhc
Mi mal, perdón por la confusión. Intentar sacar la cosa (¿valor? ¿Propiedad?) De la matriz estaba haciendo que mi cabeza girara (el anidamiento, devolución de llamada, matriz, elemento, objeto, cadena, valor, corchetes, corchetes ...). Eliminé mi uso de eval y aún obtuve la propiedad (¿valor?) De la matriz (objeto? Elemento?) Que quería.
Dave
10

la forma en que uso jsonp como a continuación:

function jsonp(uri) {
    return new Promise(function(resolve, reject) {
        var id = '_' + Math.round(10000 * Math.random());
        var callbackName = 'jsonp_callback_' + id;
        window[callbackName] = function(data) {
            delete window[callbackName];
            var ele = document.getElementById(id);
            ele.parentNode.removeChild(ele);
            resolve(data);
        }

        var src = uri + '&callback=' + callbackName;
        var script = document.createElement('script');
        script.src = src;
        script.id = id;
        script.addEventListener('error', reject);
        (document.getElementsByTagName('head')[0] || document.body || document.documentElement).appendChild(script)
    });
}

luego use el método 'jsonp' como este:

jsonp('http://xxx/cors').then(function(data){
    console.log(data);
});

referencia:

JavaScript XMLHttpRequest usando JsonP

http://www.w3ctech.com/topic/721 (hablar sobre la forma de uso de Promise)

Derek
fuente
1
terminar las asignaciones script.src = src; añade el ';' hasta el final de todas las asignaciones
chdev77
5
/**
 * Loads data asynchronously via JSONP.
 */
const load = (() => {
  let index = 0;
  const timeout = 5000;

  return url => new Promise((resolve, reject) => {
    const callback = '__callback' + index++;
    const timeoutID = window.setTimeout(() => {
      reject(new Error('Request timeout.'));
    }, timeout);

    window[callback] = response => {
      window.clearTimeout(timeoutID);
      resolve(response.data);
    };

    const script = document.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.src = url + (url.indexOf('?') === -1 ? '?' : '&') + 'callback=' + callback;
    document.getElementsByTagName('head')[0].appendChild(script);
  });
})();

Muestra de uso:

const data = await load('http://api.github.com/orgs/kriasoft');
Konstantin Tarkus
fuente
1
No olvide window[callback] = nullpermitir que la función sea recolectada como basura.
Sukima
3

Escribí una biblioteca para manejar esto, de la manera más simple posible. No es necesario que sea externo, es solo una función. A diferencia de otras opciones, este script se limpia después de sí mismo y se generaliza para realizar más solicitudes en tiempo de ejecución.

https://github.com/Fresheyeball/micro-jsonp

function jsonp(url, key, callback) {

    var appendParam = function(url, key, param){
            return url
                + (url.indexOf("?") > 0 ? "&" : "?")
                + key + "=" + param;
        },

        createScript = function(url, callback){
            var doc = document,
                head = doc.head,
                script = doc.createElement("script");

            script
            .setAttribute("src", url);

            head
            .appendChild(script);

            callback(function(){
                setTimeout(function(){
                    head
                    .removeChild(script);
                }, 0);
            });
        },

        q =
            "q" + Math.round(Math.random() * Date.now());

    createScript(
        appendParam(url, key, q), function(remove){
            window[q] =
                function(json){
                    window[q] = undefined;
                    remove();
                    callback(json);
                };
        });
}
Fresheyeball
fuente
2

Encuentre el siguiente JavaScriptejemplo para hacer una JSONPllamada sin JQuery:

Además, puede consultar mi GitHubrepositorio como referencia.

https://github.com/shedagemayur/JavaScriptCode/tree/master/jsonp

window.onload = function(){
    var callbackMethod = 'callback_' + new Date().getTime();

    var script = document.createElement('script');
    script.src = 'https://jsonplaceholder.typicode.com/users/1?callback='+callbackMethod;

    document.body.appendChild(script);

    window[callbackMethod] = function(data){
        delete window[callbackMethod];
        document.body.removeChild(script);
        console.log(data);
    }
}

Mayur S
fuente
0
/**
 * Get JSONP data for cross-domain AJAX requests
 * @private
 * @link http://cameronspear.com/blog/exactly-what-is-jsonp/
 * @param  {String} url      The URL of the JSON request
 * @param  {String} callback The name of the callback to run on load
 */
var loadJSONP = function ( url, callback ) {

    // Create script with url and callback (if specified)
    var ref = window.document.getElementsByTagName( 'script' )[ 0 ];
    var script = window.document.createElement( 'script' );
    script.src = url + (url.indexOf( '?' ) + 1 ? '&' : '?') + 'callback=' + callback;

    // Insert script tag into the DOM (append to <head>)
    ref.parentNode.insertBefore( script, ref );

    // After the script is loaded (and executed), remove it
    script.onload = function () {
        this.remove();
    };

};

/** 
 * Example
 */

// Function to run on success
var logAPI = function ( data ) {
    console.log( data );
}

// Run request
loadJSONP( 'http://api.petfinder.com/shelter.getPets?format=json&key=12345&shelter=AA11', 'logAPI' );
Chris Ferdinandi
fuente
¿Por qué window.document.getElementsByTagName('script')[0];y no document.body.appendChild(…)?
Sukima
¿No logAPIdebería establecerse en nullcuando se hace para que se pueda realizar la recolección de basura?
Sukima
0

Si está utilizando ES6 con NPM, puede probar el módulo de nodo "fetch-jsonp". Fetch API Brinda soporte para realizar una llamada JsonP como una llamada XHR normal.

Requisito previo: debería utilizar el isomorphic-fetchmódulo de nodo en su pila.

Rajendra kumar Vankadari
fuente
0

Simplemente pegando una versión ES6 de la buena respuesta de sobstel:

send(someUrl + 'error?d=' + encodeURI(JSON.stringify(json)) + '&callback=c', 'c', 5)
    .then((json) => console.log(json))
    .catch((err) => console.log(err))

function send(url, callback, timeout) {
    return new Promise((resolve, reject) => {
        let script = document.createElement('script')
        let timeout_trigger = window.setTimeout(() => {
            window[callback] = () => {}
            script.parentNode.removeChild(script)
            reject('No response')
        }, timeout * 1000)

        window[callback] = (data) => {
            window.clearTimeout(timeout_trigger)
            script.parentNode.removeChild(script)
            resolve(data)
        }

        script.type = 'text/javascript'
        script.async = true
        script.src = url

        document.getElementsByTagName('head')[0].appendChild(script)
    })
}
just_user
fuente