Wrap async function in promise

Viewed 1257

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()
    });
  }
};
1 Answers

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()
};
Related