Problem understanding json request order, promises, and callbacks

Problem understanding json request order, promises, and callbacks
0.0 0

#1

I’ve been trying for a long time to figure out how to get my json back in the same order that I send it out. I think I’m having a hard time understanding how callbacks and promises work. I’d be grateful if anyone could help me understand what I have to do.

I’ve read a lot of documentation and watched videos, I just seem to be missing some things. It’s been a while, and sometimes it feels like I understand less every time I look at it now. It’s getting just a little bit frustrating…

I know I can finish the challenge differently, but I’d like to figure this out.

thank you.


#3

Hi Andrew,

I’m currently working through this project as well and had similiad problems. I managed to get it working, but since I can’t wrap my head around promises and callbacks it’s definitely not the best/most optimal/industry standard solution.

So what I did was something like this:

let streamData = [
{ name: 'name1 },
{ name: 'name2 )
];

streamData.forEach((stream) =>  {
  $.ajax({
    url: 'user api query here',
    method: 'GET',
    success: data => {
      // Here I get user data like display name and logo url and add it to streamData 
      stream.logo = data.logo // etc.
    },
    complete: () => { // complete runs after $.ajax success or error so i put my second query here
      $.ajax({
        url: 'streams api query url',
        method: 'GET',
        success: data => {
       // I pull stream status, game name etc. here and add it to streamData the same way as above
        }
      });
   });
});

Then I use

$(document).ajaxStop()

function to build my html using streamData array that now contains all the data I need. ajaxStop runs after all ajax request are finished. If you run more Ajax somewhere on the site this solution will be wonky. The order of streams on the list does not matter to me so this might need more work if you want the list to always be ordered the same way (I guess you could just sort the array in ajaxStop()).

I may have missed some brackets in the code, markdown is not my strong suite.


#4

I’ll give it a go since I’ll likely have to explain it to my sister at some point! :smile: To begin with a probably simplistic (but conceptually easier to deal with) view of a callback is that it is simply a function, say, fnC, that is passed into another function, say, fnA, as an argument. The purpose of doing so is often because fnA produces results that have some other use. Consider the following example:

function furJede(array, fn) {
  const len = array.length;

  for (let i = 0; i < len; i++) {
    const item = array[i];

    fn(item);
  }
}

function count(i) {
  console.log(i);
}

furJede([1, 2, 3], count);
// Logs the following in the console:
// 1
// 2
// 3

Assuming that you have understood the code above, and in the context of the example given, the function count is supplied as a callback to the function furJede in the following line:

furJede([1, 2, 3], count);

That’s really all there is to it—it’s a function, or callback, that’s supplied to another function that gets called in the latter at some point. Let’s suppose we want to add the function furJede to an array as a method, we will rewrite the furJede function as follows:

function furJede(fn) {
  const len = this.length;
  // Notice that we now use 'this' instead of supplying
  // the array that we want to process as an argument
  // Since we are attaching it to an array, 'this' will become
  // the array itself

  for (let i = 0; i < len; i++) {
    const item = this[i];

    fn(item);
  }
}

function count(i) {
  console.log(i);
}

const arr = [1, 2, 3];

// Recall that an array is just an object, so we can attach
// a function to it like so:
arr.furJede = furJede;

// We can now call the function as follows, and we will
// supply the function, count, as a callback
arr.furJede(count);

// Logs the following in the console:
// 1
// 2
// 3

Alternatively, and without declaring the callback function (count in our case) beforehand, the function call arr.furJede(count) can be written with an anonymous function as:

arr.furJede(function(element) {
  console.log(element);
});

// Or with an arrow function
arr.furJede((element) => {
  console.log(element);
});

At this point you are probably thinking that the function furJede looks seriously like Array.prototype.forEach method:

var arr = [1, 2, 3];

arr.forEach((element) => {
  console.log(element);
});

// Logs the following in the console:
// 1
// 2
// 3

And you would be right because furJede was designed to do something very similar to forEach. If you are familiar with forEach, the function that you give to it as an argument is a callback; in fact, you have probably been using callbacks without thinking that they are callbacks:

const arr = [1, 2, 3];

// Array.prototype.map
const mapped = arr.map(function(val) {
  return 'number  ' + val;
});

console.log(mapped); // ['number 1', 'number 2', 'number 3'];

// The callback is:
// function(val) {
//  return 'letter  ' + val;
// }
const arr = [1, 2, 3];

// Array.prototype.filter
const filtered = arr.filter(function(val) {
  return val < 3;
});

console.log(filtered); // [1, 2]

// The callback is:
// function(val) {
//  return val < 3;
// }

That’s really (I think!) all there is to callback—it’s really is just a function you give to another function as an argument, which (the callback) gets called at some point inside the function you supply to.

If it’s clear what callbacks are at this point (hopefully so!), and if I’m not mistaken, callbacks may or may not be involved with Promise(s) (more often than not they are)—so let’s forget about callbacks for now! It looks like you do have working code, but I assume that it’s a general question and you don’t seem to be the only one having troubles with it, so moving along, consider the following code:

// Asynchronous API call using the fetch Web API
// There are callbacks! But forget about them for now!
// Don't worry about too much about the fetch API if you are not
// familiar with it—just treat it simply as an asynchronous action for now!

let quote;

fetch('https://myjson.api.com/api')
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    quote = data;
    console.log('Fetched: ', quote); // Fetched: { quote: 'Nyanpasu!' } 
  });

console.log(quote);

The expected output in the log would be in this specific order:

// 'undefined'
// Fetched: { quote: 'Nyanpasu!' }

The reason is because fetch is asynchronous, so any code that comes after it would be executed immediately after it starts. Since it takes some time to get data from a remote location, the line console.log(quote) will run before console.log('Fetched: ', quote);, therefore you get undefined.

This is where Promises come in; as you already know, a Promise lets you do something, wait for your code to signal that some operations (most likely async!) has finished (Promise.resolve, for example), and gives you the option to do things afterwards (Promise.then, for example).

The promise constructor, as you have already seen in the MDN documentation and used in your code, takes the following (simplified) form:

new Promise(function(resolve) { ... } );

The basic idea is that you do something inside the function inside the constructor and use the resolve method to indicate that things are done when, well, it’s done. Using our example from above, if we want the line console.log(quote) to also return an object, we can rewrite it using a Promise as follows:

let quote;

const promise = new Promise(function(resolve) {
  fetch('https://myjson.api.com/api')
    .then(function(response) {
      return response.json();
    })
    .then(function(data) {
      quote = data;
      console.log('Fetched: ', quote);

      resolve(); // New line
    });
});

promise.then(function() {
  console.log(quote);
});

The expected output will now ibe n this specific order:

// Fetched: { quote: 'Nyanpasu!' }
// { quote: 'Nyanpasu!' }

There are two key points to note here:

  1. The function, resolve, “returns a Promise object that is resolved with the given value” (from the documentation). In other words, it indicates that you are done waiting for whatever you wanted to wait to finish and ready to move on
  2. The promise object returned by the resolve method has a then() method, which accepts a callback function that is, given the point above, only executed once the promise that returns the promise object is resolved

The following code should now be a lot more straightforward to understand, not least because you have done it in your code (see comments):

// We are not declaring a global variable, quote, anymore.
// Instead, we are going to pass the quote from the promise
// to the callback function inside then using resolve();

const promise = new Promise(function(resolve) {
  fetch('https://myjson.api.com/api')
    .then(function(response) {
      return response.json();
    })
    .then(function(data) {
      quote = data;
      console.log('Fetched: ', quote);

      resolve(quote); // This line changed—the promise is now resolved with quote
    });
});

promise.then(function(val) { // This lines has also changed—the callback now takes an argument
  console.log(val); // This line also changed—the value comes from the callback argument 
});

The output of the code above is still:

// Fetched: { quote: 'Nyanpasu!' }
// { quote: 'Nyanpasu!' }

To avoid confusing you, something that I deliberately avoided pointing out earlier is that fetch is actually returns a resolved promise—that’s why the then method is available to it. So the code is actually a bit of a silly example since you can just do everything inside the second then after fetch in this particularly example; I wanted to avoid using setTimeout because you may have also been confused by the example in the documentation (I personally had lot of trouble just because of the syntax).

By the same token, and with respect to your code, it is worth noting that jQuery.getJSON actually returns a Promise-like object that is thenable, so the Promise constructors in your code is actually a bit redundant:

  var myPromise = new Promise((resolve, reject)=>{
    resolve($.getJSON("https://wind-bow.glitch.me/twitch-api/users/"+val+"?callback=?"))      
  });

  // ...

  myPromise.then((data)=>{
    // Do things with data
  });

… is functionally identical:

  $.getJSON("https://wind-bow.glitch.me/twitch-api/users/"+val+"?callback=?")
    .then((data) => {
      // Do things with data
    });

Now that you (hopefully) have a good grasp of how promises work, the last thing that I think you may find useful is Promise.all().

Currently in your code, you are iterating through a list of usernames and making API calls for each of them; since the responses from the API arrive at different times every time, the order of the users is actually (very likely to be) different every time you refresh your app. Promise.all lets you deal with an array of promises and only do something after all of them have been resolved. In conjunction with Array.prototype.map, you can keep things the same order (if you wish to) like this:

const users = ['Alice', 'Bob', 'Callum'];
const promises = users.map((user) => {
  return new Promise((resolve) {
    $.getJSON('https://api.com/' + user)
      .then((data) => {
        resolve(data);
      });
  });
});

Promise.all(promises).then((arrayOfUserData) => {
  arrayOfUserData.forEach((user) => {
    // Do things!
  });
});

While Promises is not the only way to deal with async actions (have a look at async function if you are interested), it’s probably not (at least currently not) a good idea to gloss over it and/or find structures that approximate/work around it.

I hope that helps and hopefully there isn’t anything wrong! I’m also happy to expand on it if anything is missing. Good luck! :smile:


#5

Thanks, I appreciate the answer, I’ve been going through it and it’s helping me understand things a bit better.
To be clear, my code does not work. I’m trying to get the information back in order, and i can’t, I don’t really know what to do. I’ve read a lot of documentation on a lot of different stuff. I just cant figure out how to apply it to my code. My promises don’t need to return anything after i use them, so my functions for displaying info are separate from each other, but i want them to run in order.


#6

Thanks, I 've never heard of ajaxStop before.