1

I'm trying to use PayPal API REST from NodeJS, so i have to make async calls with Axios, but i'm doing this on separarted classes, i have two classes:

const axios = require("axios");

/**PayPal main class, this class will setup PayPal credentials when instance */
class PayPal {

   constructor() {
        return new Promise(async (resolve, reject) => {
            await this.setupEnvironment();
            resolve();
        });
    }

    setupEnvironment = async () => {
        
        // Sets base URL to send requests
        this.setAPIDomain();

        // Sets PayPal endpoints
        this.setPayPalEndpoints();

        // Sets API Keys
        await this.setAPIKeys();

    }

    setAPIKeys = async () => {

        const paypal_credentials = this.getPayPalCredetials();
        const { client_id, client_secret } = paypal_credentials;

        try {

            const response = await axios({
                method: "post",
                url: this.endpoints.get_access_token,
                data: "grant_type=client_credentials",
                headers: {
                  "Accept-Language": "en_US",
                  "Accept": "application/json"
                },
                auth: {
                    username: client_id,
                    password: client_secret
                },
            });

            this.access_token = response.data.access_token;
            
        } catch (error) {
            console.log(error.response.data);
            throw new Error(error.response.data.error_description);
        }


    }

}

class Customer extends PayPal() {

    constructor() {
        super();
        // Customer class do some others actions
    }

}


$paypal_gateway = new Customer();

As you can see, in the PayPal class is a method (setAPIKeys) who sends a request to PayPal to generate mi access token, if i put a console.log(this.access_token) i'm getting my access token, but it only happens if i put it inside the setAPIKeys method (I'm omiting some methods of this class here).

If i put console.log($paypal_gateway.access_token) i'm getting undefined, and that's obvious 'cause the PayPal class constructor is returning a promise, but in the Customer class constructor i'm just calling the super() method without an async way, i tried to put "async" on the left of super() but it doesn't works, i also tried this:

class Customer extends PayPal {

    constructor() {

        return new Promise(async (resolve, reject) => {
            // Call to parent constructor
            super();

            // Set Customer properties
            this.setCustomer();
            resolve();
        });

    }

}

But also doesn't works, if i put a console.log(this.access_token) under the super() function i'm also getting undefined, so i don't know what can i do to await this super() constructor, can you help me please?

Carlos Tinnelly
  • 503
  • 1
  • 3
  • 20
  • Constructor no-one returns value. You can create method and then await this method. – Veyis Aliyev Oct 06 '20 at 14:55
  • 3
    Personally I would not have async code inside a constructor - just move it out to a method e.g. `init()`, and await that. – sdgluck Oct 06 '20 at 14:55
  • You could use `super().then(() => /* your code here */)` – Pointy Oct 06 '20 at 14:56
  • 1
    Returning from a constructor is not a good idea,..eg. `var c = new Customer()` c will not not be an instance of Customer,... A common approach is like mentioned, create some sort of init function. – Keith Oct 06 '20 at 15:03
  • Does this answer your question? [Async/Await Class Constructor](https://stackoverflow.com/questions/43431550/async-await-class-constructor) – jonrsharpe Oct 06 '20 at 15:14

2 Answers2

0

The constructor must return an object -- specifically an instance of the class it's defined in. It cannot return a Promise, which is what an async function does. You will have to implement a setup step. You could make an async factory function which instantiates the object and performs all setup steps, and returns the resulting object.

async function createPaypal {
  const paypal = new PayPal()
  await paypal.setupEnvironment()
  return paypal;

}

Cameron Sima
  • 5,086
  • 6
  • 28
  • 47
  • That's right, but the problems is that i have to instance Customer class, not PayPal class, but it gives me an idea – Carlos Tinnelly Oct 06 '20 at 15:17
  • 1
    If the Customer class extends PayPal then it too will have the setupEnvironment method, and it should make no real difference – Cameron Sima Oct 06 '20 at 15:20
  • The `createPaypal` function hard codes the usage of a `PayPal` instance. This would be more dynamic if you would create this as a static method on the `PayPal` class. `static async create(...args) { const paypal = new this(...args); await paypal.setupEnvironment(); return paypal; }` This static method is also available on classes extending `PayPal`. eg. `const customer = Customer.create()` which creates an instance of the `Customer` class, due to the dynamic `new this(...)` call. Where `this` refers to the class. – 3limin4t0r Oct 06 '20 at 16:43
-2

I solve it with help of @sdgluck comment, i avoid the constructor method and implements an async init method:

const axios = require("axios");

/**PayPal main class, this class will setup PayPal credentials when instance */
class PayPal {

    setupEnvironment = async () => {
        
        // Sets base URL to send requests
        this.setAPIDomain();

        // Sets PayPal endpoints
        this.setPayPalEndpoints();

        // Sets API Keys
        await this.setAPIKeys();

    }

    setAPIKeys = async () => {

        const paypal_credentials = this.getPayPalCredetials();
        const { client_id, client_secret } = paypal_credentials;

        try {

            const response = await axios({
                method: "post",
                url: this.endpoints.get_access_token,
                data: "grant_type=client_credentials",
                headers: {
                  "Accept-Language": "en_US",
                  "Accept": "application/json"
                },
                auth: {
                    username: client_id,
                    password: client_secret
                },
            });

            this.access_token = response.data.access_token;
            
        } catch (error) {
            console.log(error.response.data);
            throw new Error(error.response.data.error_description);
        }


    }

}

class Customer extends PayPal() {

    async init = () => {
        await this.setupEnvironment();
        // More init code
    }

}

const paypal_gateway = new Customer();
await paypal_gateway.init(); // <- code inside another async function

Hope this help to others, thanks a lot!

Carlos Tinnelly
  • 503
  • 1
  • 3
  • 20