-1

I have a constructor which fetches data from a DynamoDB using promisified dynogels to populate part of the object's properties. So after instantiating an instance of that object, the property is not populated, here is an extract of the code:

export class QueryAuthoriser {
  authPerms: [AuthPerms];

  constructor (data: string) {
    AuthPermsDDB.scan().execAsync().then ( (perms) => {
      perms.Items.forEach(element => {
        this.authPerms[element.name] = <AuthPerms> element.attrs
      })
    }).catch (err => {
      console.log ('%%%%%%%%%%%%%% Err loading authPerms: ', err)
    })
  }

  authFieldAccess (fieldName: string, args?:any): Promise<boolean> {
    return new Promise ((resolve, reject) => {
      console.log ('________________ authFieldAccess called for: ', fieldName)
      console.log ('________________ this.authPerms entry: ', this.authPerms[fieldName])
      resolve (true)
    })
[...]
}

So when authFieldAccess method is called, the field this.authPerms is undefined. How can I fix this?

Thanks, I'm learning node and typescript the hard way :O

Carlos Delgado
  • 552
  • 7
  • 23

1 Answers1

1

You generally don't want to carry out an asynchronous operation in a constructor because it complicates creating an object and then knowing when the async operation is done or if it had an error because you need to allow the constructor to return the object, not a promise that would tell you when the async operation was done.

There are several possible design options:

Option #1: Don't do any async operation in the constructor. Then, add a new method with an appropriate name that does the async operation and returns a promise.

In your case, you could make the new method be scan() that returns a promise. Then, you'd use your object by creating it and then calling scan and then using the returned promise to know when the data is valid.

I don't know TypeScript myself, so I'll give a modified version of your code, but the concept is the same either way whether it's TypeScript or plain Javascript:

export class QueryAuthoriser {
  authPerms: [AuthPerms];

  constructor (data: string) {
  }

  scan () {
    return AuthPermsDDB.scan().execAsync().then ( (perms) => {
      perms.Items.forEach(element => {
        this.authPerms[element.name] = <AuthPerms> element.attrs
      })
    }).catch (err => {
      console.log ('%%%%%%%%%%%%%% Err loading authPerms: ', err)
    })
  }

}

// usage
let obj = new QueryAuthoriser(...);
obj.scan(...).then(() => {
    // the object is full initialized now and can be used here
}).catch(err => {
    // error here
})

Option #2: Initiate the async operation in the constructor and use a promise in the instance data for the caller to know when everything is done.

export class QueryAuthoriser {
  authPerms: [AuthPerms];

  constructor (data: string) {
    this.initialScan = AuthPermsDDB.scan().execAsync().then ( (perms) => {
      perms.Items.forEach(element => {
        this.authPerms[element.name] = <AuthPerms> element.attrs
      })
    }).catch (err => {
      console.log ('%%%%%%%%%%%%%% Err loading authPerms: ', err)
    })
  }

}

// usage
let obj = new QueryAuthoriser(...);
obj.initialScan.then(() => {
    // the object is full initialized now and can be used here
}).catch(err => {
    // error here
});

Option #3: Use a factory function that returns a promise that resolves to the object itself.

export createQueryAuthorizer;

function createQueryAuthorizer(...) {
    let obj = new QueryAuthorizer(...);
    return obj._scan(...).then(() => {
        // resolve with the object itself
        return obj;
    })
}

class QueryAuthoriser {
  authPerms: [AuthPerms];

  constructor (data: string) {
  }

  _scan () {
    return AuthPermsDDB.scan().execAsync().then ( (perms) => {
      perms.Items.forEach(element => {
        this.authPerms[element.name] = <AuthPerms> element.attrs
      })
    }).catch (err => {
      console.log ('%%%%%%%%%%%%%% Err loading authPerms: ', err)
    })
  }

}

// usage
createQueryAuthorizer(...).then(obj => {
    // the object is fully initialized now and can be used here
}).catch(err => {
    // error here
});

My preference is for option #3 for several reasons. It captures some shared code in the factory function that every caller has to do in the other schemes. It also prevents access to the object until it is properly initialized. The other two schemes just require documentation and programming discipline and can easily be misused.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks @jfriend00, it works nicely. Although this code would block the `createQueryAuthorizer` method, whereas my intention is to make an Async call to the DB and only wait the first time I need the data, but only the first time, because the data will be needed several times. I'll try to change the implementation, though. – Carlos Delgado Aug 02 '17 at 08:08
  • @CarlosDelgado - Nothing here blocks so I'm not quite sure what you mean by that. The `createQueryAuthorizer()` method returns immediately and returns a promise. You can't use your object until it is properly initialized. If other parts of your code also want to use the same object, they can wait on the same promise. – jfriend00 Aug 02 '17 at 13:21