Search code examples
javascriptjsonxmlhttprequest

JavaScript: XMLHttpRequest returning 'undefined' string on first run of script; I need it to load during the script


Preface: I'm a novice at JS, have no formal training in it, and usually make things on the fly by researching what I am trying to do. That failed this time.

I am currently trying to make a short JS script that will serve as a bookmarklet. The intent is to leverage the Tinder API to show users of Tinder some of the profile pictures of users who liked them, normally available with the Gold Feature.

Currently, it looks like this:

var stringz;
var xhr = new XMLHttpRequest();
var tokez = localStorage.getItem("TinderWeb/APIToken");
var url = "https://api.gotinder.com/v2/fast-match/teasers?locale=en";
xhr.withCredentials = true;
xhr.open("GET", url);
xhr.setRequestHeader("accept", "application/json");
xhr.setRequestHeader("content-type", "application/json; charset=utf-8");
xhr.setRequestHeader("x-auth-token", tokez);
xhr.setRequestHeader("tinder-version", "2.35.0");
xhr.setRequestHeader("platform", "web");
xhr.send();
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        stringz = xhr.responseText;
        return stringz;
    }
};
//Turn the xhr response into a JSON string
var jasonstring = JSON.parse(stringz);
//Grab the URLs
var jasonstrung = jasonstring.data.results.map(x => x.user.photos.map(y => y.url));
//Turn the URLs into a nicely formatted JSON string
var jason = JSON.stringify(jasonstrung, null, 4);
//See what we got
console.log(jason);

The reason I am doing both JSON.parse and JSON.stringify is that the returned data from the xhr is a text string formatted like JSON but it isn't actually JSON yet so I have to parse it in order to grab the pieces I want, then format them after so they aren't a goopy block (although the stringify part isn't super necessary)

On the first run of this in the Chrome Dev Console, it spits out the following:

VM5418:1 Uncaught SyntaxError: Unexpected token u in JSON at position 0
    at JSON.parse (<anonymous>)
    at <anonymous>:18:24

My assumption as to why it does this is because stringz is not yet "filled up" and returns as "undefined" when JSON.parse tries to cut through it.

However, once the script completes, if one were to type console.log(stringz), the expected string appears! If one runs the entire script 2x, it prints out the final desired dataset:

[
    [
        "https://preview.gotinder.com/5ea6601a4a11120100e84f58/original_65b52a4a-e2b2-4fdb-a9e6-cb16cf4f91c6.jpeg"
    ],
    [
        "https://preview.gotinder.com/5a4735a12eced0716745c8f1/1080x1080_9b15a72b-10c3-47c6-8680-a9c1ff6bdbf7.jpg"
    ],
    [
        "https://preview.gotinder.com/5e8d4231370407010088281b/original_adb4a1e3-06c0-4984-bca1-978200a5a311.jpeg"
    ],
    [
        "https://preview.gotinder.com/5ea77de583887d0100f385b8/original_af32971d-6d80-4076-a0f8-92ab54f820b3.jpeg"
    ],
    [
        "https://preview.gotinder.com/5bf7a1a29c0764cc3409bb02/1080x1350_c9784773-b937-4564-8c96-1a380832fdab.jpg"
    ],
    [
        "https://preview.gotinder.com/5d147c0560364e16004bcf5e/original_bf550230-baba-4d70-8c75-da64a9ce1b6c.jpeg"
    ],
    [
        "https://preview.gotinder.com/5c9ca2c2c8a4501600a979aa/original_915f4c0f-eb58-4283-bc58-00fdadc3c33c.jpeg"
    ],
    [
        "https://preview.gotinder.com/541efb64f5d81ab67f4b599f/original_7f11dea4-41c8-4e9c-8c7a-0c886484a076.jpeg"
    ],
    [
        "https://preview.gotinder.com/5a8b56376c220c1f5d8b43d9/original_7c19a078-8bd7-48f9-8e30-123b8f937814.jpeg"
    ],
    [
        "https://preview.gotinder.com/5d0c18341ea6e416002bfb1d/original_41d203ce-d116-4714-a223-90ccfd928ff2.jpeg"
    ]
]

Is there any way to make this thing work in one go (bookmarklet style)? setTimeout doesn't work unfortunately, assuming it is a problem in terms of taking too long to fill "stringz" before I use JSON.parse on it.

Thank you!


Solution

  • The problem is coming from that XHR makes your function asynchronous: it sends a request and the response arrives later - during that time your next (and next, and next,....) lines of code are executed.

    You have to start your JSON string transformation when the response has already arrived - that means you should place your code xhr.onreadystatechange (I had to comment out a lot of things so the snippet works):

    var stringz;
    var xhr = new XMLHttpRequest();
    // var tokez = localStorage.getItem("TinderWeb/APIToken");
    // var url = "https://api.gotinder.com/v2/fast-match/teasers?locale=en";
    var url = "https://jsonplaceholder.typicode.com/posts";
    // xhr.withCredentials = true;
    xhr.open("GET", url);
    // xhr.setRequestHeader("accept", "application/json");
    // xhr.setRequestHeader("content-type", "application/json; charset=utf-8");
    // xhr.setRequestHeader("x-auth-token", tokez);
    // xhr.setRequestHeader("tinder-version", "2.35.0");
    // xhr.setRequestHeader("platform", "web");
    
    xhr.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        // the response arrives here
        stringz = this.responseText;
    
        // start your JSON transformation when the
        // response arrives
        jsonTransform(stringz)
        return stringz;
      }
    };
    
    xhr.send();
    
    // this part of code will be executed synchronously - this
    // doesn't wait until your response arrives
    var synchronous = 'this will be logged before response arrives'
    
    console.log(synchronous)
    
    function jsonTransform(stringz) {
      //Turn the xhr response into a JSON string
      var jasonstring = JSON.parse(stringz);
      //Grab the URLs
      // var jasonstrung = jasonstring.data.results.map(x => x.user.photos.map(y => y.url));
      //Turn the URLs into a nicely formatted JSON string
      // var jason = JSON.stringify(jasonstrung, null, 4);
      //See what we got
      const jason = JSON.stringify(jasonstring)
      console.log(jason);
    }

    Another method

    I suggest you use fetch() instead of xhr - with xhr you have to take care of everything - fetch() is quite new, with a Promise based syntax (you'll be meeting that a lot, if you work with APIs)

    More on fetch():

    const url = "https://jsonplaceholder.typicode.com/posts";
    
    // fetch returns a Promise, so you can use
    // .then, .catch, .finally - very handy!
    fetch(url)
      .then(resp => {
        return resp.json()
      })
      .then(json => {
        // you have the json formatted response here:
        console.log(json)
      })
      .catch(err => {
        console.log(err)
      })

    You could use fetch() with the friendly async-await syntax, that makes your code feel synchronous:

    const url = "https://jsonplaceholder.typicode.com/posts";
    
    // await can only be placed in an async function!
    async function fetchAPI(url) {
      // try-catch block to handle errors of the fetch()
      try {
        const response = await fetch(url)
        const json = await response.json()
        console.log(json)
      } catch (err) {
        console.log(err)
      }
    }
    
    fetchAPI(url)