2

I am working on creating a simple web application that handles requests and communicates with a SQL db using Express and Sequelize. Currently I am running into a problem when calling an async function on an object, the this part of the object is undefined. I am wrapping the functions in the express-async-handler to handle the promises returns, strangely if I remove the handler wrapper on the async method I am able to access this but as expected does not handle errors correctly. What am I misunderstanding/doing incorrectly? Below is the code:

userController.js

var models  = require('../models');
const asyncHandler = require('express-async-handler')

var user = models.User

exports.registerUser = asyncHandler(async function(req, res) {
  var username = req.body.username,
      password = req.body.password;
      u = user.build({
        username: username, password: password
      })
  
      await u.registerUser()
      res.send('hello world')
  }
);

user.js not working

const bcrypt = require('bcrypt');
const asyncHandler = require('express-async-handler')

module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define('User', {
    id: {
      type: DataTypes.UUID,
      defaultValue: sequelize.literal('uuid_generate_v4()'),
      primaryKey: true
    },
    name: DataTypes.STRING,
    username: DataTypes.STRING,
    password: DataTypes.STRING,
  }, {
    paranoid: true,
  });

  // Associations
  User.associate = function(models) {
    // associations can be defined here
    User.hasMany(models.Cat, {
      foreignKey: 'userId',
      as: 'cats',
      onDelete: 'CASCADE',
    })
  };

  // Instance methods
  User.prototype.registerUser = asyncHandler(async function () {
    await bcrypt.hash(this.password, 10, (err, hash) => {  // not able to access 'this'
      if(err) throw err;

      // Set the hashed password and save the model
      this.password = hash;
      this.save()
    });
  })
};

user.js remove asyncHandler() working

const bcrypt = require('bcrypt');
const asyncHandler = require('express-async-handler')

module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define('User', {
    id: {
      type: DataTypes.UUID,
      defaultValue: sequelize.literal('uuid_generate_v4()'),
      primaryKey: true
    },
    name: DataTypes.STRING,
    username: DataTypes.STRING,
    password: DataTypes.STRING,
  }, {
    paranoid: true,
  });

  // Associations
  User.associate = function(models) {
    // associations can be defined here
    User.hasMany(models.Cat, {
      foreignKey: 'userId',
      as: 'cats',
      onDelete: 'CASCADE',
    })
  };

  // Instance methods
  User.prototype.registerUser = async function () {
    await bcrypt.hash(this.password, 10, (err, hash) => {
      if(err) throw err;

      // Set the hashed password and save the model
      this.password = hash;
      this.save()
    });
  }
};
walshbm15
  • 113
  • 1
  • 11
  • It sounds like `asyncHandler` doesn't pass along the `this` it's called with when calling your handler. There's not much you can do about that other than not using it, or (better) forking the project, fixing it (which should be really easy), and sending them a pull request... :-) – T.J. Crowder Jun 30 '20 at 15:35
  • How are you calling `register` (etc.)? – T.J. Crowder Jun 30 '20 at 16:04
  • Btw the `u` in your controller is an accidentally global variable. – Bergi Jun 30 '20 at 16:10

1 Answers1

2

I am wrapping the functions in the express-async-handler to handle the promises returns

No, you don't need to do that. The promise returned by your User.prototype.registerUser method is handled just fine with the line

await u.registerUser()

You only need the asyncHandler wrapper on Express route handlers, which don't know how to handle promises returned by async functions.

If I remove the handler wrapper on the async method I am able to access this, but as expected does not handle errors correctly.

The asyncHandler doesn't help with handling that error either. The problem is that you do

if(err) throw err;

inside an asynchronous callback, which is just an unhandled exception crashing your app. Instead, you need to promisify bcrypt.hash so that you get a promise that will be rejected. Or just use the promise version:

User.prototype.registerUser = async function () {
  const hash = await bcrypt.hash(this.password, 10)
  this.password = hash;
  await this.save()
};
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you that worked, and makes sense! To add one thing, I also had to add `await` to `this.save()` otherwise errors when calling that were not getting handled. – walshbm15 Jun 30 '20 at 16:51
  • @walshbm15 Yeah, makes sense, I forgot that `save()` returns a promise as well – Bergi Jun 30 '20 at 16:52