Javascript API Example question

My issue is probably super easy, but I’ve been banging my head on it for a few hours so it’s probably time to ask…

In the example:

var devicesPr = particle.listDevices({ auth: token });

devicesPr.then(
  function(devices){
    console.log('Devices: ', devices);
  },
  function(err) {
    console.log('List devices call failed: ', err);
  }
);

This lists out my devices to the console no problem, but using the data stored in devicesPr is making me nuts.

devicesPr looks to be JSON, but I cannot for the life of me pull out its contents any other way. I’ve attempted JSON.Parse, JSON. Stringify, console.log(devicePr[0]), fs.writefile() etc…ad nauseam.

What is the datatype in this variable and how the heck do I get at it?!?

Advise super appreciated.

That should be JS ‘promise’: https://www.google.nl/amp/s/scotch.io/tutorials/javascript-promises-for-dummies/amp

Any reason in particular you’d like to mess with that? All the relevant data should be in ‘devices’.

The goal is to take the data in devicePr and loop through it. Here’s a PowerShell example as I’m not sure how to write this in JS, yet.

foreach ($device in $devicePr){
  if ($device.id -eq "23423452342etc.."){do sql stuff}
}

I’ve attempted to console.log(devicePr.devices) and console.log(devicePr.devices[0]) with not a whole lot of luck.

How do promises fit into this? I could see that this would be a promise.

var devicesPr = particle.listDevices({ auth: token });

But once that line is run wouldn’t the promise be fulfilled as the data is stored in the variable?

Hmm, if you have no purposeful intentions of doing anything with the promise itself, I’d not mess with it. The data you need is available in the ‘devices’ object returned by the promise upon completion. The example does show the console.log(devices), which contains all the information you’d need.
You can then loop through that with a ‘for each (device in devices)’, where you take the devices object rather than the promise.

I’ve not quite figured it out a 100% myself, but the first line makes a promise object and tells it what action to complete. The second part is when it’s finished, with a success or error, and respective arguments are passed in those functions.
So the first line only tells it to start executing the action, whereas the second part is where it’s been completed and the data is available. As such, the devicePr won’t contain actual data, but is purely the promise object.

I think…

You still haven’t explained why you are trying to do anything with devicesPr in the first place. As @Moors7 alluded, you want to use the devices object.

Because this is an async callback, you really should be doing whatever you need to do with it inside of the chained callback. Here is how I would translate your PowerShell example into nodejs:

var devicesPr = particle.listDevices({ auth: token });

devicesPr.then(
  function(devices){
    console.log('Devices: ', devices);
    devices.forEach(function(device) {
      if (device.id === "23423452342etc..") {
        sqlStuffFunction(device.id);
      }
    });
  },
  function(err) {
    console.log('List devices call failed: ', err);
  }
);
1 Like

This is likely my inexperience with JS, but I would normally access data in an array by dot notations i.e. devicesPr.devices.body.id

I did have some luck with this, which does print out the ID of the device:

devicesPr.then(
  function(devices){
 console.log(devices.body[0].id);

},
  function(err) {
    console.log('List devices call failed: ', err);
  }
);

However, if I simply console.log(devices.body[0].id) or console.log(devicePr.devices.body[0].id) outside of the devicesPr.then{}, I get a variety of undefined errors.

When this is run, what is happening? I would assume it’s reaching out and getting the data, correct?

var devicesPr = particle.listDevices({ auth: token });

If so, is a promise required to loop through its contents, or can we loop and console.log in another way?

So there are several things going on. Broadly speaking, you are misinterpreting the scope of where the reference to devices is valid. devices is only valid inside of the function that defines it as a variable function(devices) {...}

But, so that you can better understand:

Let’s pretend that particle.listDevices was a Promise function we ourselves defined:

class particle {
  listDevices(tokenObject) {
    return new Promise(
      function (call_this_function_if_succeeded, call_this_function_if_failed) {
          if (isValidToken(tokenObject) && have_list_to_return) {
              var devicesList = [ device1, device2, device3 ];
              call_this_function_if_succeeded(devicesList); // resolve
          } else {
              var error_context = new Error('Could not get the devices list');
              call_this_function_if_failed(error_context); // reject
          }
      });
  };
}

A Promise is in essence a function with two mandatory arguments, a function that should get called if the task succeeds, and a function that should get called if the task fails. Why is this? A Promise allows you to do things asynchronously. This way you don’t have to actually wait for the response, and can do other things. Instead of just waiting, you pre-define what you want your program to do after the Promise is done doing whatever it’s going to do.

The devicesPr object is a wrapper on that whole complex structure containing the function callback references. It’s not data itself, but rather a variable containing the details of what how your program expects to hear back from the Promise function.

To use the results of this we use the devicesPr.then(...) terminology. A Promise object has a built in function, then which does the following:

class Promise {
  ...
  then(call_this_function_if_succeeded, call_this_function_if_failed) {
    this.my_call_this_function_if_succeeded = call_this_function_if_succeeded;
    this.my_call_this_function_if_failed = call_this_function_if_failed;
    return;
  }
}

So when you call devicesPr.then(functionA, functionB), you are assigning devicesPr.my_call_this_function_if_succeeded = functionA and devicesPr.my_call_this_function_if_failed = functionB.

In your case, for brevity’s sake, we define the functions inside of the function call for devicesPr.then. You could just as easily structure it this way:

var devicesPr = particle.listDevices({ auth: token });

function handleSuccessfulListDevicesCall(devices) {
  if (device.id === "23423452342etc..") {
    sqlStuffFunction(device.id);
  }
}

function handleErrorsFromListDevicesCall(error) {
  console.log('List devices call failed: ', err);
}

devicesPr.then(handleSuccessfulListDevicesCall, handleErrorsFromListDevicesCall);

The only place the devices object exists is within the internals of particle.listDevices, and inside of whatever function you define as handleSuccessfulListDevicesCall(devices) {...}

I don’t know any PowerShell, but my assumption is that all variables seem to be essentially global. In JavaScript, all variables are very strongly limited to the context where they are defined, and any children of that context.

So great, let’s just copy the devices list to a global variable:

// Please don't do this!!
var devicesPr = particle.listDevices({ auth: token });

var global_devices_list = [];

function handleSuccessfulListDevicesCall(devices) {
  global_devices_list = devices;
}

function handleErrorsFromListDevicesCall(error) {
  console.log('List devices call failed: ', err);
}

devicesPr.then(handleSuccessfulListDevicesCall, handleErrorsFromListDevicesCall);

global_devices_list.forEach(
  function (device) { // this never gets called because array is empty!!!!
    if (device.id === "23423452342etc..") {  
      sqlStuffFunction(device.id);
    }
  }
);

Because a Promise is merely a promise that something will happen in the future, the javascript just keeps marching through the rest of your code until it reaches the end, and then eventually the Promise returns and calls the things you have defined to handle it.

The code above will try to loop through an empty array of devices because the promise has not yet populated it. Thus you have to wait until the result of the Promise is ready for you.

The .then terminology is what “waits” for the result. It is also what allows you to handle errors if things don’t work properly.

Think of your JavaScript code like an outline of what you want to have happen. Not everything happens in the order that you put it on the page, you are simply defining the relationships between things and what happens in response to what. Asynchronous stuff is super confusing.

At the end of all of this, what you should do is either what I mentioned in my last comment, or something like this:

var devicesPr = particle.listDevices({ auth: token });

function handleSuccessfulListDevicesCall(devices) {
  // does absolutely everything you need to do with
  // the devices list inside of here.  You can call
  // other things but be sure to pass in devices as
  // an argument

  // like you mentioned you wanted to do:
  if (device.id === "23423452342etc..") {
    sqlStuffFunction(device.id);
  }

  // do all the rest of the stuff you need to do after
  // the SQL stuff is done HERE not outside of the fn
  doTheRestOfTheStuffFunction();
}

function handleErrorsFromListDevicesCall(error) {
  console.log('List devices call failed: ', err);
}

devicesPr.then(handleSuccessfulListDevicesCall, handleErrorsFromListDevicesCall);

People do the inline functions because it’s a little easier to read the flow once you understand them. But if the scope is too confusing, try the above to simplify that a little bit, maybe. Hope this is helpful / didn’t confuse you more haha.

6 Likes

Fantastic! I sincerely appreciate the explanation, I need to digest this a little bit, but I think I see what your saying… Now I need to play around a bit…

1 Like

Just wanted to pop-in and say thanks again, the JS code I was playing with is working.

The biggest thing that threw me was this: (a quote from a question on StackOverflow)

Keep in mind that promises and deferred objects are just containers for a future value, they are not the value itself.

For example, in a Powershell request to the webAPI:

$all_devices = Invoke-RestMethod -Uri "https://api.particle.io/v1/devices?access_token=$accessToken"

The value of $all_devices will contain an object array of all your devices. I was expecting the same when seeing:

  var devicesPr = particle.listDevices({
      auth: config.authToken
  });

and boy o boy is that wrong. :smiley:

1 Like

that is so true. Amazing explanation by the way. Thank you Justice.

2 Likes