0

I'm trying to make an api call using the callback method in request, but I'm new to web development and I'm stuck on async at the moment. I've got the following working, but I want to break the logic out some. For example, here's what is working currently

const request = require('request');

class GetAllData{

    constructor(){
        this.loadData();
    }

    // Gets all data from api query that I'll need to build everything else I'll need
    data(callback){
        request({'url':`https://definitely/a/url.json`, 'json': true }, function (error, response, body) {
            callback(body);
        });
    }

    loadData(cmrUrl){
        console.log("loadData");
        this.data(function(result){ console.log(result.foo.bar)});
    }
}

var moreData = new GetAllData();

This works, and I can do the two things I need, which are log some results, and make a small calculation with the results. However, if I want to break this logic out into other functions, I get some errors.

const request = require('request');

class GetAllData{

    constructor(){
        this.loadData();

    // Member variables
        this._subsetOne;
        this._thingICalculated;

    // Function call to print data outside of the async call.
        this.printData(this._subsetOne, this._thingICalculated);
    }

    // Gets all data from api query that I'll need to build everything else I'll need
    data(callback){
        request({'url':`https://definitely/a/url.json`, 'json': true }, function (error, response, body) {
            callback(body);
        });
    }

    loadData(cmrUrl){
        console.log("loadData");

                    // Set a class member variable
                    // ERROR: 'this' is undefined
                    this.data(function(result){ this._subsetOne = result.foo.bar)};

            // Call a member function, which calculates something, and then sets a member variable.
            this.calculateSomething = result;
            console.log(result);


        };
    }

    // Function which takes in result from async call, then calculates something.
    set calculateSomething(result){
        this._thingICalculated = result + 1;
    }

    printData(x, y){
        console.log(x,y);
    }
}

var moreData = new GetAllData();

From what I've been reading the issues I'm hitting are pretty common, but I'm still not understanding why this isn't working since the call is asyncronous, and I'm just trying to set a variable, or call a function. I'm assuming there's some way to ask the member variable setting and function call to await the completion of the async request?


Fix attempt one

const request = require('request');

class GetAllData{

    constructor(){
        this.loadData();
        this._subset;
    }

    // Gets all data from api query that I'll need to build everything else I'll need
    data(callback){
        request({'url':`https://definitely.a.url/yep.json`, 'json': true }, function (error, response, body) {
            callback(body);
        });
    }

    loadData(cmrUrl){
        console.log("loadData");
        this.data(function(result){ this._subset = result
        this.getSubset()}.bind(this));
    }

    getSubset(){
        console.log(this._subset);
    }
}

var moreData = new GetAllData();

Subset ends up being undefined.

trueCamelType
  • 2,198
  • 5
  • 39
  • 76
  • `this` is undefined because it's being called like: `callback(body)`. You'll need to use `apply` or `call` to set up `this`. – MinusFour Aug 02 '19 at 23:26
  • I don't really understand what you're asking, but to use asynchronous operations in a constructor (which is looks like you might be doing) requires a different design pattern in order to allow code to know when those asynchronous things are done. See [Asynchronous operations in a constructor](https://stackoverflow.com/questions/49905178/asynchronous-operations-in-constructor/49906064#49906064) for various design patterns for doing this. – jfriend00 Aug 02 '19 at 23:32
  • How did you come to using a setter to pass in the callback to a request; did you see it in an example somewhere? – ChiefTwoPencils Aug 02 '19 at 23:36
  • @ChiefTwoPencils No, I didn't see someone else use a setter this way. I originally wrote it as a setter, and then read about callback, and just never changed it. It felt like a good idea at the time, because I thought about it in terms of getter and setter for the large dataset I'm getting from the API query. Maybe it was a poor choice. – trueCamelType Aug 02 '19 at 23:37
  • @jfriend00 I don't have to call these in the constructor, and I probably won't be, but thanks for the information, in case I have to. I'll read through it now. – trueCamelType Aug 02 '19 at 23:38
  • 1
    Thanks, just curious; I haven't seen it either. It seems a little convoluted to me but that's not really the problem. – ChiefTwoPencils Aug 02 '19 at 23:42
  • 2
    Now you have a different problem. The callback function for your async function hasn't even been called by the time you call `moreData.getSubset()`. I recommend you read: [How do I return the response from an asynchronous call?](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) – MinusFour Aug 03 '19 at 03:34

1 Answers1

0

In the constructor, you have to bind any member method that uses this to itself. So before calling loadData:

this.loadData = this.loadData.bind(this);

Also seems like this.data will get its own scope in loadData, which is why this returns undefined inside that function. Thus you have to bind this.data to this (your class instance) as well.

And do the same for any method that accesses this. The problem is classic JavaScript functions by default have an undefined scope. Arrow functions however automatically inherit the scope of the caller.

Enioluwa Segun
  • 911
  • 8
  • 12
  • `loadData` is fine. The problem is the function in `this.data`. That's the one that needs the `this` binded. – MinusFour Aug 02 '19 at 23:30
  • no matter which one I add `this.loadData = this.loadData.bind(this)`, or `this.data = this.data.bind(this)` I get the same issue of this not being defined. – trueCamelType Aug 03 '19 at 02:28
  • 1
    @trueCamelType it would need to be bind on `this.data = function(){..}.bind(this)` because your setter triggers the `request` call. There's also no getter for `this.data` so `this.data.bind(this)` shouldn't work. – MinusFour Aug 03 '19 at 02:38
  • @MinusFour Thanks, I've changed the function from a setter so there's less confusion, and it really didn't need to be a setter. I've added another section implementing the fix as best I understood. The new `getSubset()` function returns `undefined`. – trueCamelType Aug 03 '19 at 03:18