0

I have this code:

const request = require('request');

let url = "https://api.warframe.market/v1/items";

let options = {json: true};
let item_urls;
//const response;
request(url, options, (error, res, body) => {
    if (error) {
        return  console.log(error)
    };

    if (!error && res.statusCode == 200) {
        console.log(body);
        item_urls = body.payload.items;

    };
});

It works fine and returns the JSON to the variable item_urls.

I then tried to wrap my own function around it like so:

const request = require('request');

let url = "https://api.warframe.market/v1/items";

let options = {json: true};
let item_urls;
//const response;

function getitems(){
request(url, options, (error, res, body) => {
    if (error) {
        return  console.log(error)
    };

    if (!error && res.statusCode == 200) {
        console.log(body);
        item_urls= body.payload.items;

    };
});
};
getitems()

But it doesn't work, item_urls ends up being undefined. The code is identical except for being wrapped inside my function. In case you are wondering why I want to it wrapped inside a function - I need to pass a parameter to it but I've removed this logic for simplicity and to demonstrate the issue.

Thanks,

EDIT: Additional details I forgot to mention sorry.

So I am running this code directly line by line in my IDE and sending the lines to the node REPL so that I can test code line by line. If we look at the first block of code:

I highlight it all and run it. Since request is async, I wait for it to complete first, I know it's completed because the callback function outputs the body to the console, so I wait until I see a load of data get printed to the console, this is how I verify the command completed successfully. Straight after the line of code which outputs the body to the console it then dumps the body into my variable. To confirm this has worked I then type item_urls at the REPL terminal itself and can see the output.

With the second block of code, I open a new REPL so everything is clean. I then execute all the lines of code again and wait... What I am expecting is for the request async call to take a few seconds to complete like the first code block. However, nothing get's outputted to the console. I wait up to 30 secs (the previous block of code only took around 5 secs) then I type item_urls at the REPL terminal again and it says undefined.

I am convinced it has something to do with the async and callback nature but I can't figure out what.

Mucker
  • 257
  • 3
  • 12

1 Answers1

1

You have to use item_urls INSIDE the callback in which you first get its value. The only way you can use that value is to use it right there in the callback itself or in a function you call from there and pass it the value.

The issue is that request() is asynchronous and non-blocking. That means that you execute request(), it starts the operation and then it returns and goes right on executing more code (including the code where you try to use item_urls). But, at that point, the callback has not yet been called so it's still undefined.

In the end, the ONLY place that you know item_urls has a value is inside that callback. So, any code that uses that value needs to go inside that callback or get called from inside that callback. That's how asynchronous programming works in node.js.

So, it would need to look like this:

function getitems(){

    request(url, options, (error, res, body) => {
        if (error) {
            return  console.log(error)
        }

        if (!error && res.statusCode == 200) {
            console.log(body);
            // use body.payload.items directly here
            // or call some function here and pass it the value
            // Do not assign it to some higher scoped variable and
            // expect to use it elsewhere.  Can't do that.
        }
    });
}

getitems()

If you want to get the value outside of getitems, then you need to use either a callback, and event or a promise to communicate the asynchronous retrieved value outside of getitems(). You can see a full discussion of why you can't just return the value from getitems() here in this highly popular answer: How do I return the response from an asynchronous call.


I don't know what you think doesn't work about the 2nd code block where the code is in a function. I just put this into a file by itself and executed it and it works just fine. So, if you remove item_urls from the equation and you use the result inside the callback like I suggested above, it all works just fine, even in the second code block.

Here's the exact program I just ran:

const request = require('request');

let url = "https://api.warframe.market/v1/items";

let options = {
    json: true
};
function getitems() {
    request(url, options, (error, res, body) => {
        if (error) {
            return console.log(error);
        }

        if (res.statusCode === 200) {
            console.log(body.payload.items);
        }
    });
}
getitems()

This code works. It gives me output like this:

[
  {
    id: '5835a4564b0377e226bdc360',
    url_name: 'axi_c1_intact',
    thumb: 'icons/en/thumbs/Axi_C1_Intact.99413ebefc5b87d57d9e5314265b56de.128x128.png',
    item_name: 'Axi C1 Intact'
  },
  {
    id: '5835a4dd4b0377e226bdc380',
    url_name: 'meso_s3_intact',
    thumb: 'icons/en/thumbs/Meso_S3_Intact.caee59471a7b06ca040f2d257083e008.128x128.png',
    item_name: 'Meso S3 Intact'
  },
  {
    id: '58d8f31c11efe42a5e523215',
    url_name: 'lith_n2_exceptional',
    thumb: 'icons/en/thumbs/Lith_N2_Exceptional.b82a140ba17908be7226fddcecd7bf62.128x128.png',
    item_name: 'Lith N2 Exceptional'
  },
  {
    id: '5936dd6916efa3a742b15f48',
    url_name: 'lith_n3_exceptional',
    thumb: 'icons/en/thumbs/Lith_N3_Exceptional.b82a140ba17908be7226fddcecd7bf62.128x128.png',
    item_name: 'Lith N3 Exceptional'
  },
  {
    id: '5835a4404b0377e226bdc358',
    url_name: 'neo_v4_intact',
    thumb: 'icons/en/thumbs/Neo_V4_Intact.e0e38afae723757487927845b35a81d2.128x128.png',
    item_name: 'Neo V4 Intact'
  },

  .... a whole bunch more items
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • But as I've already said, the first block of code works, and the item_urls variable in outside of the callback there also. – Mucker Apr 17 '20 at 04:46
  • @Mucker - No, you're mistaken about the first block of code. If you immediately try to use `item_urls` in the first block of code, it will be `undefined`. Put a `console.log(item_urls)` right after the call to `request()`. It will be `undefined`. If you wait long enough, it will eventually get a value, but there's no reliable way with this coding structure to know when it will get a value. Either use the value inside the callback function OR use one of the techniques I've outlined above for communicating the value to the outside world. – jfriend00 Apr 17 '20 at 04:49
  • @Mucker - I've cleaned up your code to make it easier to read (just fixed indentation and remove some superfluous semi-colons). I also added a `console.log(item_urls)` to your first block of code. That will show `undefined` so I don't know what you think is working in the first block of code. – jfriend00 Apr 17 '20 at 04:54
  • Yep you are right jfriend, I didn't explain fully that I was executing it line by line. Have a look at my post again where I've added EDIT and it will be a bit clearer now why the var should be populated. – Mucker Apr 17 '20 at 05:01
  • @Mucker - So, you just whacked all my edits to try to fix your formatting and make your code more readable. I tried to improve your question to make it easier to understand and to show you where things were wrong, but you just trashed all those edits (maybe unintentionally, maybe intentionally, I don't know). I've said my piece here. I guess I'll leave now. Neither one of your code blocks works properly. My answer explains what you have to do to use an asynchronous response. You can either ask me questions about it or wait for someone else to come along and explain it differently. – jfriend00 Apr 17 '20 at 05:04
  • sorry dude that wasn't my intention; I was editing the post obviously at the same time you were. So my edit didn't have your edits in and it overwritten yours. Didn't do it on purpose. I understand my code won't work properly, if you haven't guessed yet I'm fairly new to it all. The code isn't complete. At some point I was going to add some way to handle the callbacks as I learn more. I can see how your code would fix it but I am trying to understand where my code is wrong; I don't just want the answer, I want to know why. Hope that makes sense and I really appreciate you explaining it all! – Mucker Apr 17 '20 at 05:11
  • To make this discussion easier forget about the items_url var. Both code blocks excute console.log(body); INSIDE the callback but only the first one works. Now I get that the items_url var is an issue but I think this is a bit of a red herring because even it wasn't there, the console.log still wouldn't work. Would you mind explaining why? So I can learn from this and not make the same mistake next time. – Mucker Apr 17 '20 at 05:13
  • @Mucker - See what I added to the end of my answer. Your second code block works just fine if you follow my advice and get rid of `item_urls` and use the result inside the callback. I put that code in a stand-alone program and ran it. It works. The code is inside the `getitems()` function and it works. I show a portion of the output it generates. – jfriend00 Apr 17 '20 at 05:30
  • OK i get it now and I have it working my side too. thanks for your patience :-) – Mucker Apr 17 '20 at 05:45