10

I've a problem about async/await on typescript with target es2017. Below is my code :

my route.ts :

method: 'POST',
        config: {            
            auth: {
                strategy: 'token',        
            }
        },
        path: '/users',
        handler: (request, reply) {

    let { username, password } = request.payload;

    const getOperation = User
    .findAll({
        where: {
            username: username
        }
    })
    .then(([User]) => {
        if(!User) {
            reply({
                error: true,
                errMessage: 'The specified user was not found'
            });
            return;
        }

        let comparedPassword = compareHashPassword(User.password, password);
        console.log(comparedPassword);

        if(comparedPassword) {
            const token = jwt.sign({
                username,
                scope: User.id

            }, 'iJiYpmaVwXMp8PpzPkWqc9ShQ5UhyEfy', {
                algorithm: 'HS256',
                expiresIn: '1h'
            });

            reply({
                token,
                scope: User.id
            });
        } else {
            reply('incorrect password');
        }
    } )
    .catch(error => { reply('server-side error') });

};

my helper.ts :

export async function compareHashPassword(pw:string, originalPw:string) {
    console.log(pw);
    console.log(originalPw);
    let compared = bcrypt.compare(originalPw, pw, function(err, isMatch) {
        if(err) {
                return console.error(err);
        }
        console.log(isMatch);
        return isMatch;
        });

    return await compared;
}

this auth route supposed to return JWT token when user login. but the problem here is even when I enter the valid password to sign-in the function compareHashPassword always return undefined.

For example when i call the api with json string

{
  "username": "x",
  "password": "helloword"
}

When i track using console.log(), the log is :

$2a$10$Y9wkjblablabla -> hashed password stored in db
helloword 
Promise {
  <pending>,
  domain: 
   Domain {
     domain: null,
     _events: { error: [Function: bound ] },
     _eventsCount: 1,
     _maxListeners: undefined,
     members: [] } }
true

maybe this is just my lack of understanding about using async/await with typescript. for note my env is :

node : v8.6.0
typescript : v2.5.2
ts-node : v3.3.0
my tsconfig.json //
{
    "compilerOptions": {
        "outDir": "dist",
        "target": "es2017",
        "module": "commonjs",
        "removeComments": true,
        "types": [
            "node"
        ],
        "allowJs": true,
        "moduleResolution": "classic"
    },
    "exclude": [
        "node_modules"
    ]
}
kola
  • 269
  • 2
  • 5
  • 11

2 Answers2

19

In order for async/await to work, an async function has to return a Promise. Also, you must call an async with the await keyword this way:

You can only call await inside an async function, so I wrapped it in an async func

const someFunc = async function() {
    return new Promise((resolve, reject) => { 
        setTimeout(resolve, 100, true);
    });
};

(async () => {
     const result = await someFunc(); // true
})();

You are not satisfying any of these rules in your compareHashPassword declaration and the way you call it.

This is how I would rewrite your code :

export async function compareHashPassword(pw:string, originalPw:string) {
    return new Promise((resolve, reject) => {
        bcrypt.compare(originalPw, pw, function(err, isMatch) {
            if(err) {
                reject(err);
            }
            console.log(isMatch);
            resolve(isMatch);
        });
    });
}

// and call it this way
(async () => {
     const compared = await compareHashPassword(pw, originPw);
})()

Have a look at this async await blog post: https://ponyfoo.com/articles/understanding-javascript-async-await

And this blog post for Promises: https://developers.google.com/web/fundamentals/primers/promises

Also as @Patrick Roberts mentioned, you can use util.promisify to turn an async callback style function into a Promise, this is node 8.xx otherwise you can use bluebird package which has a similar functionality.

Hope this helps

Omkar Yadav
  • 523
  • 1
  • 6
  • 14
Francois
  • 3,050
  • 13
  • 21
  • _As for now, you can only call await inside an async function_ As per the specification, that will **always** be the case, just saying. – Patrick Roberts Sep 27 '17 at 10:17
  • I find that sad, although I didn't check the implementation itself in v8. Thanks for the tip, I will update my answer – Francois Sep 27 '17 at 10:19
  • 2
    By the way, in your first example, the `Promise.resolve(...)` is redundant. All you have to do is `return true` and the `async` function will implicitly coerce it into a resolved promise. – Patrick Roberts Sep 27 '17 at 10:40
  • I dind't know that, so explicitely return a Promise only needs to be done when having a callback style async call ? – Francois Sep 27 '17 at 10:41
  • Yes, or in node.js you have the option of using `util.promisify()` to convert callback-style functions into promise-style functions. See my answer for usage. – Patrick Roberts Sep 27 '17 at 10:43
  • 2
    Now your `new Promise()` is no longer redundant, but I personally prefer writing it like this `setTimeout(resolve, 100, true);` Just food for thought~ – Patrick Roberts Sep 27 '17 at 10:45
5

Since you're targeting ES2017, let's clean up your code, starting with the simpler helper.ts:

import { promisify } from 'util' // you'll thank me later

const compare = promisify(bcrypt.compare)

export async function compareHashPassword(pw:string, originalPw:string) {
    console.log(pw);
    console.log(originalPw);
    return compare(originalPw, pw);
}

Now for the route.ts:

handler: async (request, reply) => {
  let { username, password } = request.payload;

  try {
    const [user] = await User.findAll({
      where: {
        username: username
      }
    })

    if(!user) {
      reply({
        error: true,
        errMessage: 'The specified user was not found'
      });

      return;
    }

    let match = await compareHashPassword(user.password, password);

    console.log(match);

    if (match) {
      const token = jwt.sign({
        username,
        scope: User.id
      }, 'iJiYpmaVwXMp8PpzPkWqc9ShQ5UhyEfy', {
        algorithm: 'HS256',
        expiresIn: '1h'
      });

      reply({
        token,
        scope: user.id
      });
    } else {
      reply('incorrect password');
    }
  } catch (error) {
    reply('server-side error')
  }
}

Hopefully I've matched what you're attempting to accomplish, based on the code you've provided. If there's an issue somewhere with this updated code, please let me know in the comments.

Patrick Roberts
  • 49,224
  • 10
  • 102
  • 153
  • wow, that's really helpful utility from node. Thanks for all your answer. Both of your solution worked and help me understand how async works. cheers. – kola Sep 28 '17 at 03:22