Como fazer uma solicitação JSONP a partir do Javascript sem JQuery?

122

Posso fazer uma solicitação JSONP entre domínios em JavaScript sem usar o jQuery ou outra biblioteca externa? Gostaria de usar o próprio JavaScript, analisar os dados e torná-los um objeto para que eu pudesse usá-los. Preciso usar uma biblioteca externa? Se não, como posso fazer isso?

Dave
fonte

Respostas:

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
fonte
2
Aqui está um JSBin que pode ser usado para mexer com o JSONP da Wikipedia. Foi referenciado nesta resposta .
rkagerer
1
Eu acho que vale a pena ressaltar que a resposta deve ter a seguinte forma: foo(payload_of_json_data)a idéia é que, quando carregada na tag de script, chama a função foo com a carga útil já como um objeto javascript e nenhuma análise é necessária.
Oct
@ WillMunn Eu não acho isso possível com o JSONP. É um truque desde os dias anteriores ao CORS. Para que você precisa definir cabeçalhos? O servidor precisa especificamente aceitar solicitações JSONP, portanto, ele deve ser configurado para servir de maneira sã.
Matt Bola
você está certo, eu li mal a documentação da API, existe um parâmetro de consulta especial para fazer o que eu queria ao usar as aplicações jsonp.
quer
@WillMunn não se preocupe. Fico feliz que você foi capaz de resolver o problema!
Matt Bola
37

Exemplo leve (com suporte para onSuccess e onTimeout). Você precisa passar o nome de retorno de chamada na URL, se necessário.

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 da amostra:

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

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

sobstel
fonte
29

O que é JSONP?

O importante a lembrar com o jsonp é que na verdade não é um protocolo ou tipo de dados. É apenas uma maneira de carregar um script rapidamente e processar o script que é introduzido na página. No espírito do JSONP, isso significa introduzir um novo objeto javascript do servidor no aplicativo / script do cliente.

Quando o JSONP é necessário?

É um método de permitir que um domínio acesse / processe dados de outro na mesma página de forma assíncrona. Principalmente, é usado para substituir as restrições do CORS (compartilhamento de recursos de origem cruzada) que ocorreriam com uma solicitação XHR (ajax). As cargas de script não estão sujeitas a restrições CORS.

Como isso é feito

A introdução de um novo objeto javascript no servidor pode ser implementada de várias maneiras, mas a prática mais comum é o servidor implementar a execução de uma função de 'retorno de chamada', com o objeto necessário passado para ele. A função de retorno de chamada é apenas uma função que você já configurou no cliente que o script que você carrega chama no ponto em que o script carrega para processar os dados passados ​​para ele.

Exemplo:

Eu tenho um aplicativo que registra todos os itens na casa de alguém. Meu aplicativo está configurado e agora quero recuperar todos os itens no quarto principal.

Meu aplicativo está ativado app.home.com. As APIs das quais eu preciso carregar os dados estão ativadas api.home.com.

A menos que o servidor esteja configurado explicitamente para permitir isso, não posso usar o ajax para carregar esses dados, pois mesmo as páginas em subdomínios separados estão sujeitas a restrições XHR CORS.

Idealmente, configure as coisas para permitir XHR no domínio x

Idealmente, como a API e o aplicativo estão no mesmo domínio, talvez eu tenha acesso para configurar os cabeçalhos api.home.com. Se assim for, posso adicionar um Access-Control-Allow-Origin: item de cabeçalho que conceda acesso app.home.com. Supondo que o cabeçalho esteja configurado da seguinte maneira Access-Control-Allow-Origin: "http://app.home.com":, isso é muito mais seguro do que configurar o JSONP. Isso ocorre porque ele app.home.compode obter tudo o que deseja api.home.comsem api.home.comdar acesso ao CORS a toda a Internet.

A solução XHR acima não é possível. Configurar o JSONP No script do meu cliente: configurei uma função para processar a resposta do servidor ao fazer a chamada JSONP. :

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

O servidor precisará ser configurado para retornar um mini-script parecido com algo como "processJSONPResponse('{"room":"main bedroom","items":["bed","chest of drawers"]}');"Ele pode ser projetado para retornar essa sequência se algo como //api.home.com?getdata=room&room=main_bedroomfor chamado.

Em seguida, o cliente configura uma tag de script como tal:

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

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

Isso carrega o script e chama imediatamente window.processJSONPResponse()como gravado / eco / impresso pelo servidor. Os dados passados ​​como parâmetro para a função agora são armazenados na dataFromServervariável local e você pode fazer o que precisar.

Limpar

Uma vez que o cliente tenha os dados, ie. imediatamente após o script ser adicionado ao DOM, o elemento de script pode ser removido do DOM:

script.parentNode.removeChild(script);
orvalho
fonte
2
Muito obrigado, isso me ajudou muito no meu projeto. Uma questão menor: recebi SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data. Depois de adicionar aspas simples para os dados, tudo funcionou bem, então:"processJSONPResponse('{"room":"main bedroom","items":["bed","chest of drawers"]}');"
Hein van Dyke
17

Meu entendimento é que você realmente usa tags de script com JSONP, então ...

A primeira etapa é criar sua função que manipulará o JSON:

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

Verifique se essa função está acessível em nível global.

Em seguida, adicione um elemento de script ao DOM:

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

O script carregará o JavaScript que o provedor da API cria e o executa.

sdleihssirhc
fonte
2
Obrigado a todos. Consegui filmar pela internet em busca de alguns dados e depois faço algo com eles. Usei eval () nos dados de resposta que me ajudaram a lidar com a matriz de objetos que ela é (acho). {Era um urso imaginando a análise com minha capacidade cerebral limitada, mas finalmente consegui o valor extra}. Fantástico.
Dave
@ Dave @ Matt Talvez eu estou confuso sobre JSONP, mas você não precisa evalou parsenem nada. Você deve obter o JavaScript que o navegador pode executar, certo?
Sdleihssirhc
Meu mal, desculpe pela confusão. Tentar tirar a coisa (value? Property?) Da matriz estava fazendo minha cabeça girar (aninhamento, retorno de chamada, matriz, elemento, objeto, string, valor, colchetes, colchetes ...). Eu removi meu uso de eval e ainda obtive a propriedade (value?) Da matriz (objeto? Elemento?) Que eu queria.
Dave
10

do jeito que eu uso jsonp como abaixo:

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)
    });
}

use o método 'jsonp' como este:

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

referência:

JavaScript XMLHttpRequest usando JsonP

http://www.w3ctech.com/topic/721 (fale sobre o modo de uso Promise)

derek
fonte
1
finalize as atribuições script.src = src; Adicione o ';' para o fim de todas as atribuições
chdev77
6

Eu tenho uma biblioteca javascript pura para fazer isso https://github.com/robertodecurnex/J50Npi/blob/master/J50Npi.js

Dê uma olhada e informe-me se precisar de ajuda para usar ou entender o código.

Aliás, você tem um exemplo simples de uso aqui: http://robertodecurnex.github.com/J50Npi/

robertodecurnex
fonte
6
Sua solução foi um pouco simples demais para o meu caso de uso, então eu adicionei múltipla pedido de apoio gist.github.com/1431613
Chad Scira
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);
  });
})();

Amostra de uso:

const data = await load('http://api.github.com/orgs/kriasoft');
Konstantin Tarkus
fonte
1
Não se esqueça de window[callback] = nullpermitir que a função seja coletada no lixo.
Sukima 4/08/16
3

Eu escrevi uma biblioteca para lidar com isso, da maneira mais simples possível. Não há necessidade de torná-lo externo, é apenas uma função. Diferentemente de outras opções, esse script é limpo e é generalizado para fazer solicitações adicionais em tempo de execução.

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
fonte
2

Por favor, encontre o JavaScriptexemplo abaixo para fazer uma JSONPchamada sem o JQuery:

Além disso, você pode consultar meu GitHubrepositório para referência.

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
fonte
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
fonte
Por que window.document.getElementsByTagName('script')[0];e não document.body.appendChild(…)?
Sukima 4/08/16
Não deve logAPIser definido como nullquando terminar para que a coleta de lixo possa ser executada nele?
Sukima 04/08/19
0

Se você estiver usando o ES6 com NPM, tente o módulo de nó "fetch-jsonp". API de busca Fornece suporte para fazer uma chamada JsonP como uma chamada XHR regular.

Pré-requisito: você deve usar o isomorphic-fetchmódulo node na sua pilha.

Rajendra kumar Vankadari
fonte
0

Apenas colando uma versão ES6 da boa resposta 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
fonte