จะสร้างคำขอ JSONP จาก Javascript โดยไม่ใช้ JQuery ได้อย่างไร


122

ฉันสามารถสร้างคำขอข้ามโดเมน JSONP ใน JavaScript โดยไม่ใช้ jQuery หรือไลบรารีภายนอกอื่น ๆ ได้หรือไม่ ฉันต้องการใช้ JavaScript เองแล้วแยกวิเคราะห์ข้อมูลและทำให้มันเป็นวัตถุเพื่อที่ฉันจะได้ใช้มัน ฉันต้องใช้ไลบรารีภายนอกหรือไม่? ถ้าไม่ฉันจะทำอย่างไร?


คำตอบ:


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

2
นี่คือ JSBin ที่สามารถใช้ในการเล่นซอกับ JSONPจาก Wikipedia ได้รับการอ้างอิงในคำตอบนี้
rkagerer

1
ฉันคิดว่ามันคุ้มค่าที่จะชี้ให้เห็นว่าการตอบสนองควรอยู่ในรูปแบบ: foo(payload_of_json_data)ความคิดที่ว่าเมื่อโหลดลงในแท็กสคริปต์จะเรียกฟังก์ชัน foo ที่มี payload อยู่แล้วว่าเป็นวัตถุจาวาสคริปต์และไม่จำเป็นต้องแยกวิเคราะห์
Octopus

@WillMunn ฉันไม่คิดว่ามันจะทำได้กับ JSONP เป็นการแฮ็กตั้งแต่ช่วงก่อน CORS หลายวัน คุณต้องตั้งค่าส่วนหัวสำหรับอะไร? เซิร์ฟเวอร์จำเป็นต้องยอมรับคำขอ JSONP โดยเฉพาะดังนั้นจึงควรตั้งค่าให้แสดงผลอย่างมีเหตุผล
Matt Ball

คุณพูดถูกฉันอ่านเอกสารเกี่ยวกับ API บางรายการผิดมีพารามิเตอร์การค้นหาพิเศษที่จะทำในสิ่งที่ฉันต้องการเมื่อใช้แอพ jsonp
Will Munn

@WillMunn ไม่ต้องกังวล ดีใจที่คุณสามารถแยกแยะได้!
Matt Ball

37

ตัวอย่างน้ำหนักเบา (พร้อมการรองรับ onSuccess และ onTimeout) คุณต้องส่งชื่อเรียกกลับภายใน URL หากคุณต้องการ

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

ตัวอย่างการใช้งาน:

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

ที่ GitHub: https://github.com/sobstel/jsonp.js/blob/master/jsonp.js


29

JSONP คืออะไร

สิ่งสำคัญที่ต้องจำไว้สำหรับ jsonp คือมันไม่ใช่โปรโตคอลหรือชนิดข้อมูล เป็นเพียงวิธีการโหลดสคริปต์ในทันทีและประมวลผลสคริปต์ที่แนะนำในหน้าเว็บ ตามเจตนารมณ์ของ JSONP หมายถึงการนำอ็อบเจ็กต์ javascript ใหม่จากเซิร์ฟเวอร์เข้าสู่ไคลเอ็นต์แอ็พพลิเคชัน / สคริปต์

JSONP จำเป็นเมื่อใด

เป็นวิธีการ 1 ในการอนุญาตให้โดเมนหนึ่งเข้าถึง / ประมวลผลข้อมูลจากอีกโดเมนหนึ่งในเพจเดียวกันโดยไม่ตรงกัน โดยทั่วไปจะใช้เพื่อลบล้างข้อ จำกัด CORS (Cross Origin Resource Sharing) ซึ่งจะเกิดขึ้นกับคำขอ XHR (ajax) การโหลดสคริปต์ไม่อยู่ภายใต้ข้อ จำกัด ของ CORS

เป็นอย่างไรบ้าง

การแนะนำอ็อบเจ็กต์ javascript ใหม่จากเซิร์ฟเวอร์สามารถนำไปใช้งานได้หลายวิธี แต่วิธีปฏิบัติที่พบบ่อยที่สุดคือให้เซิร์ฟเวอร์ดำเนินการเรียกใช้ฟังก์ชัน 'เรียกกลับ' โดยส่งผ่านอ็อบเจ็กต์ที่ต้องการเข้าไป ฟังก์ชันเรียกกลับเป็นเพียงฟังก์ชันที่คุณได้ตั้งค่าไว้บนไคลเอนต์ซึ่งสคริปต์ที่คุณโหลดการโทร ณ จุดที่สคริปต์โหลดเพื่อประมวลผลข้อมูลที่ส่งผ่านไป

ตัวอย่าง:

ฉันมีแอปพลิเคชันที่บันทึกรายการทั้งหมดในบ้านของใครบางคน แอปพลิเคชันของฉันได้รับการตั้งค่าและตอนนี้ฉันต้องการเรียกคืนรายการทั้งหมดในห้องนอนหลัก

app.home.comแอพลิเคชันของฉันอยู่บน APIs api.home.comที่ฉันจำเป็นต้องโหลดข้อมูลจากที่อยู่บน

เว้นแต่ว่าเซิร์ฟเวอร์จะได้รับการตั้งค่าอย่างชัดเจนเพื่ออนุญาตฉันไม่สามารถใช้ ajax เพื่อโหลดข้อมูลนี้ได้เนื่องจากแม้แต่เพจในโดเมนย่อยที่แยกจากกันก็อยู่ภายใต้ข้อ จำกัด XHR CORS

ตามหลักการแล้วตั้งค่าเพื่ออนุญาต x-domain XHR

ตามหลักการแล้วเนื่องจาก api และแอปอยู่ในโดเมนเดียวกันฉันจึงอาจมีสิทธิ์เข้าถึงเพื่อตั้งค่าส่วนหัวapi.home.comได้ ถ้าผมทำผมสามารถเพิ่มรายการส่วนหัวอนุญาตให้เข้าถึงAccess-Control-Allow-Origin: app.home.comสมมติว่ามีการตั้งค่าส่วนหัวดังนี้Access-Control-Allow-Origin: "http://app.home.com"ซึ่งปลอดภัยกว่าการตั้งค่า JSONP มาก เนื่องจากapp.home.comสามารถรับทุกสิ่งที่ต้องการได้api.home.comโดยไม่ต้องapi.home.comให้ CORS เข้าถึงอินเทอร์เน็ตทั้งหมด

โซลูชัน XHR ข้างต้นไม่สามารถทำได้ ตั้งค่า JSONP บนสคริปต์ไคลเอ็นต์ของฉัน: ฉันตั้งค่าฟังก์ชันเพื่อประมวลผลการตอบกลับจากเซิร์ฟเวอร์เมื่อฉันทำการเรียก JSONP :

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

เซิร์ฟเวอร์จะต้องได้รับการตั้งค่าเพื่อส่งคืนมินิสคริปต์ที่มีลักษณะบางอย่างเช่น"processJSONPResponse('{"room":"main bedroom","items":["bed","chest of drawers"]}');"อาจได้รับการออกแบบให้ส่งคืนสตริงดังกล่าวหาก//api.home.com?getdata=room&room=main_bedroomมีการเรียกสิ่งที่ต้องการ

จากนั้นไคลเอนต์ตั้งค่าแท็กสคริปต์ดังนี้:

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

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

สิ่งนี้จะโหลดสคริปต์และเรียกทันทีwindow.processJSONPResponse()ตามที่เขียน / สะท้อน / พิมพ์โดยเซิร์ฟเวอร์ ข้อมูลที่ส่งผ่านเป็นพารามิเตอร์ไปยังฟังก์ชันจะถูกเก็บไว้ในdataFromServerตัวแปรโลคัลและคุณสามารถทำอะไรก็ได้

ทำความสะอาด

เมื่อลูกค้ามีข้อมูลเช่น. ทันทีหลังจากเพิ่มสคริปต์ไปยัง DOM องค์ประกอบสคริปต์สามารถลบออกจาก DOM:

script.parentNode.removeChild(script);

2
ขอบคุณมากสิ่งนี้ช่วยฉันในโครงการของฉันได้มาก ปัญหาเล็ก ๆ น้อย ๆ : SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON dataผมได้รับ หลังจากเพิ่มคำพูดเดียวลงในข้อมูลทุกอย่างก็ทำงานได้ดีดังนั้น:"processJSONPResponse('{"room":"main bedroom","items":["bed","chest of drawers"]}');"
Hein van Dyke

17

ความเข้าใจของฉันคือคุณใช้แท็กสคริปต์กับ JSONP จริงๆดังนั้น ...

ขั้นตอนแรกคือการสร้างฟังก์ชันของคุณที่จะจัดการ JSON:

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

ตรวจสอบให้แน่ใจว่าฟังก์ชันนี้สามารถเข้าถึงได้ในระดับโลก

จากนั้นเพิ่มองค์ประกอบสคริปต์ใน DOM:

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

สคริปต์จะโหลด JavaScript ที่ผู้ให้บริการ API สร้างขึ้นและเรียกใช้งาน


2
ขอบคุณทุกคน มันยิงออกไปทางอินเทอร์เน็ตเพื่อค้นหาข้อมูลบางอย่างจากนั้นฉันก็ทำอะไรกับมัน ฉันใช้ eval () กับข้อมูลการตอบสนองซึ่งช่วยฉันจัดการกับออบเจ็กต์ - อาร์เรย์ที่เป็น (ฉันคิดว่า) {มันเป็นหมีที่หาคำตอบได้ด้วยพลังสมองที่ จำกัด ของฉัน แต่ในที่สุดฉันก็ได้รับคุณค่าจากมัน} น่าอัศจรรย์
Dave

@Dave @ Matt บางทีฉันอาจamเลือนบน JSONP แต่คุณไม่ควรต้องevalหรือparseหรืออะไร คุณควรได้รับ JavaScript ที่เบราว์เซอร์สามารถเรียกใช้งานได้ใช่ไหม?
sdleihssirhc

ฉันไม่ดีขออภัยในความสับสน การพยายามดึงสิ่ง (ค่า? คุณสมบัติ?) ออกจากอาร์เรย์ทำให้หัวของฉันหมุน (การซ้อนการเรียกกลับอาร์เรย์องค์ประกอบวัตถุสตริงค่าวงเล็บปีกกาวงเล็บ ... ) ฉันลบการใช้ eval ของฉันและยังคงได้รับคุณสมบัติ (ค่า?) จากอาร์เรย์ (object? element?) ที่ฉันต้องการ
Dave

10

วิธีที่ฉันใช้ jsonp ดังนี้:

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

จากนั้นใช้วิธี 'jsonp' ดังนี้:

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

อ้างอิง:

JavaScript XMLHttpRequest โดยใช้ JsonP

http://www.w3ctech.com/topic/721 (พูดคุยเกี่ยวกับวิธีการใช้ Promise)


1
ยุติการกำหนด script.src = src; เพิ่ม ';' ในตอนท้ายของงานทั้งหมด
chdev77

6

ฉันมีไลบรารี javascript ที่บริสุทธิ์เพื่อทำเช่นนั้นhttps://github.com/robertodecurnex/J50Npi/blob/master/J50Npi.js

ลองดูและแจ้งให้เราทราบหากคุณต้องการความช่วยเหลือในการใช้หรือทำความเข้าใจโค้ด

Btw คุณมีตัวอย่างการใช้งานง่ายๆที่นี่: http://robertodecurnex.github.com/J50Npi/


6
วิธีแก้ปัญหาของคุณง่ายเกินไปสำหรับกรณีการใช้งานของฉันดังนั้นฉันจึงเพิ่มคำขอการสนับสนุนหลายรายการ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);
  });
})();

ตัวอย่างการใช้งาน:

const data = await load('http://api.github.com/orgs/kriasoft');

1
อย่าลืมwindow[callback] = nullอนุญาตให้ฟังก์ชันเป็นที่เก็บขยะ
Sukima

3

ฉันเขียนห้องสมุดเพื่อจัดการเรื่องนี้อย่างง่ายที่สุด ไม่จำเป็นต้องทำให้ภายนอกเป็นเพียงฟังก์ชันเดียว ซึ่งแตกต่างจากตัวเลือกอื่น ๆ สคริปต์นี้จะล้างข้อมูลในภายหลังและเป็นข้อมูลทั่วไปสำหรับการร้องขอเพิ่มเติมในรันไทม์

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

2

โปรดดูJavaScriptตัวอย่างด้านล่างเพื่อJSONPโทรออกโดยไม่ใช้ JQuery:

นอกจากนี้คุณสามารถอ้างอิงที่GitHubเก็บของฉันเพื่ออ้างอิง

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


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

ทำไมwindow.document.getElementsByTagName('script')[0];ไม่document.body.appendChild(…)?
Sukima

ไม่ควรlogAPIตั้งค่าเป็นnullเมื่อเสร็จแล้วจึงสามารถดำเนินการเก็บขยะได้หรือไม่?
Sukima

0

หากคุณใช้ ES6 กับ NPM คุณสามารถลองใช้โมดูลโหนด "fetch-jsonp" Fetch API ให้การสนับสนุนสำหรับการโทร JsonP เป็นการเรียก XHR ปกติ

สิ่งที่ต้องมีก่อน: คุณควรใช้isomorphic-fetchโมดูลโหนดในสแตกของคุณ


0

เพียงแค่วางคำตอบที่ดีของ sobstel เวอร์ชัน ES6:

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)
    })
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.