typescript api overlooking the async await functionality

My controller contains an asynchronous method that is supposed to set a results object. However, I'm facing an issue where instead of waiting for the 'await' to finish executing, the code jumps to the response object call prematurely, leaving the necessary variable undefined. Even in the debugger, the breakpoints in the intended method are hit after encountering the undefined error. I'm puzzled as to why the async await mechanism isn't functioning as expected here. Can anyone shed some light on this?

Controller class method:

public async loginUser(req: Request, res: Response) {
    const { name, password } = req.body;
    let result: ILoginResult = await UserData.login(name, password); // always turns out to be undefined
    res.status(result.status).send(result.result); // gets executed before 'result' is properly set
  }

UserData class:

import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import mongoose from 'mongoose';
import ILoginResult from './ILoginResult';
import UserModel from '../../models/UserModel';

class UserData {
    private connUri: string;

    constructor() {
        this.connUri = process.env.MONGO_LOCAL_CONN_URL;
    }

    public async login(name: string, password: string) {

        try {
            await mongoose.connect(this.connUri, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, }, (err) => {
                let result: ILoginResult = { status: 0, result: null, error: '', token: '' };
                let status = 200;
                if (!err) {
                    UserModel.findOne({ name }, (err, user) => {
                        if (!err && user) {
                            bcrypt.compare(password, user.password).then(match => {
                                if (match) {
                                    status = 200;
                                    const payload = { user: user.name };
                                    const options = { expiresIn: '2d', issuer: 'http://localhost' };
                                    const secret = process.env.JWT_SECRET;
                                    const token = jwt.sign(payload, secret, options);

                                    result.token = token;
                                    result.status = status;
                                    result.result = user;
                                } else {
                                    status = 401;
                                    result.status = status;
                                    result.error = `Authentication error`;
                                }
                                return result;
                            }).catch(err => {
                                status = 500;
                                result.status = status;
                                result.error = err;
                                return { status: status, result: result };
                            });
                        } else {
                            status = 404;
                            result.status = status;
                            result.error = err;
                            return result;
                        }
                    });
                } else {
                    status = 500;
                    result.status = status;
                    result.error = err.toString();
                    return result;
                }
            });
        } catch (e) {
            let result: ILoginResult;
            result.error = e.toString();
            result.status = 500;
            return result;
        }
    }
}

export default new UserData();

Answer №1

Avoid directly combining async/await with callback-based APIs.

As per the documentation, the promise returned by mongoose.connect does not necessarily wait for actions within the provided callback, even if you are using an up-to-date version.

It is recommended to perform those actions in the code following the await mongoose.connect statement.

Here is an example implementation (in UserData):

public async login(name: string, password: string) {
    let result: ILoginResult = { status: 0, result: null, error: '', token: '' };
    try {
        await mongoose.connect(this.connUri, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, });
        const user = await UserModel.findOne({ name });
        let status = 200;
        let match = await bcrypt.compare(password, user.password).catch(() => false);
        if (!match) {
            status = 401;
            result.status = status;
            result.error = `Authentication error`;
            return result;
        }
        status = 200;
        // Create a token
        const payload = { user: user.name };
        const options = { expiresIn: '2d', issuer: 'http://localhost' };
        const secret = process.env.JWT_SECRET;
        const token = jwt.sign(payload, secret, options);

        // console.log('TOKEN', token);
        result.token = token;
        result.status = status;
        result.result = user;
        return result;
    } catch (e) {
        result.error = e.toString();
        result.status = 500;
        return result;
    }
}

Answer №2

await is not compatible with traditional node-style callbacks. It specifically works with Promises, so using a callback in this context means that it will be executed asynchronously without being awaited by the await keyword.

Furthermore, incorporating a callback when already utilizing await and receiving a promise as a return value defeats the purpose of using await altogether. The code would be more efficient if written without callbacks:


        let result: ILoginResult = { status: 0, result: null, error: '', token: '' };
        try {
            await mongoose.connect(this.connUri, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, });
            let status = 200;
            let user = await UserModel.findOne({ name });
            if (user) {
                let match = await bcrypt.compare(password, user.password);
                if (match) {
                    status = 200;
                    const payload = { user: user.name };
                    const options = { expiresIn: '2d', issuer: 'http://localhost' };
                    const secret = process.env.JWT_SECRET;
                    const token = jwt.sign(payload, secret, options);

                    result.token = token;
                    result.status = status;
                    result.result = user;
                } else {
                    status = 401;
                    result.status = status;
                    result.error = `Authentication error`;
                }
                return result;
            } else {
                status = 404;
                result.status = 404;
                result.error = err;
                return result;
            }
        } catch (e) {
            result.error = e.toString();
            result.status = 500;
            return result;
        }

This approach results in cleaner code, reduces the complexity of nested callback functions, and offers a more elegant utilization of async/await functionality. By encapsulating everything within a try/catch block, any errors thrown by the await ... expressions will be caught and handled uniformly without the need for repetitive error handling sections throughout the code.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Errors occur when attempting to parse Uint8Array in Typescript

I received the following object as a response from calling the AWS Lambda client in NodeJS. { '$metadata': { httpStatusCode: 200, requestId: '1245', extendedRequestId: undefined, cfId: undefined, attempts: 1, tot ...

Within Blade, Laravel and Vue components are able to communicate by sharing data from one component to another

Is it possible to achieve this task? Here is the scenario: I have a left navbar displaying membership status (active/inactive). Once the payment gateway receives the payment, a webhook is triggered via Paddle(Laravel Paddle API). During the webhook proc ...

Three.js - Rotation does not follow local orientation accurately

In my current project, I have created an extensive array of objects centered around a focal point within a scene. My goal is to manipulate these objects along their local axes. Initially, I aligned all the objects to face the origin by using a reference ob ...

What is the best way to target the iframe within the wysihtml5 editor?

Currently, I am utilizing the wysiwyg editor called wysihtml5 along with the bootstrap-wysihtml5 extension. As part of my project, I am designing a character counter functionality that will showcase a red border around the editor area once a specific maxl ...

A step-by-step guide to implementing the PUT function in AngularJS using Mongoose

My attempt to send a GET request to the Mongo works fine, but I'm having trouble getting the PUT request to work. My suspicion is that there might be an issue with the path from the controller to the router. I've already tried using both update() ...

Having issues with importing images in Next.js using the Next Images package

Having trouble with importing images locally from the images folder. Error message: "Module not found: Can't resolve '../images/banner1.jpg'" https://i.stack.imgur.com/Dv90J.png Attempting to access images in ImagesSlider.js file at compo ...

Can you provide guidance on how to pass props to a component through a prop in React when using TypeScript?

Hey there, I'm facing an issue with TypeScript where the JavaScript version of my code is functioning properly, but I'm having trouble getting the types to compile correctly. In an attempt to simplify things for this question, I've removed ...

Why aren't my cookies being successfully created on the frontend of my application when utilizing express-session and Node.js?

Having trouble setting cookies while using express-session in my MERN stack project. When accessing endpoints from frontend localhost:3000 to backend localhost:8080, the cookies do not set properly. However, everything works fine when both frontend and b ...

Encountering an issue with the for loop in React Native when using FlatList

As a beginner in programming, I am eager to dynamically render a list. The Navbar parent component holds the state with different Food Types categories such as Mexican and Chinese, each with its corresponding menu. My goal is to display each Food Type fol ...

Steps to properly specify the Express Error Type

When defining the variable err, I have opted to use any due to uncertainty about the correct type. I was anticipating an express.Error type, but none was found. What would be the appropriate way to assign a type to err? // Addressing Syntax Error in JSON ...

Are there any drawbacks to converting all instance methods into arrow functions in order to prevent binding loss?

What are the potential drawbacks of converting all instance methods into arrow functions to avoid the "lost binding" issue? For example, when using ReactJS, the statement onClick={this.foo} can lead to lost binding, as it translates to createElement({ ... ...

The components for my children are not being displayed within the Drawer component in Material UI and React

Why are the Material UI components and other project components not displayed when I use my DrawerForm component? List of dependencies: react: 18.2.0 react-dom: 18.2.0 @amcharts/amcharts5: 5.3.6 @mui/icons-material: 5.11.11 @mui/material: 5.11.12 Code s ...

Is it possible for Nextjs routing catchAll to coexist with a slug folder within a route?

When converting my pages to ISR, I encountered an issue with handling params and dynamic routes. One example is where article/?pageNumber=2 needs to be rewritten as article/2 in middleware for improved performance. However, this change in routing structure ...

Navigational menu that slides or follows as you scroll

Wondering if anyone knows of a straightforward jQuery or Javascript method to create a navigation sidebar that smoothly moves with the user as they scroll down a page. A good example can be found here: Any suggestions would be greatly welcome. ...

Identifying the Operating System and Applying the Appropriate Stylesheet

I am trying to detect the Windows operating system and assign a specific stylesheet for Windows only. Below is the code snippet I have been using: $(function() { if (navigator.appVersion.indexOf("Win")!=-1) { $(document ...

Why bother re-rendering components in React that haven't had any changes in their state?

Within my main component, I have both a state and a static component nested inside. An issue arises when the state changes and triggers a re-render of the main component, causing the static component to also re-render unnecessarily. import { useState } fro ...

Tips for sorting an array of objects by multiple keys while maintaining the order of each key that comes before

I am looking to create a versatile function that can organize an array of objects based on specified keys, while maintaining the order of previous keys. Here is a sample scenario: const input = [ { a: 'aardvark', b: 'bear', c: 'c ...

Guide to generating TypeScript output files within a non-hierarchical directory layout

In my project, I have a directory structure with multiple typescript files organized as follows: | src | app-1 | tsconfig.json | app-2 | tsconfig.json | common | standalone | tsconfig.json For each of the ...

Optionalize keys containing a specific character in their name

Imagine I have an object similar to the following: const obj = { a: 1, "b?": 2, "c?": 3 } The current type of this object is as follows: type Obj = { a: number; "b?": number; "c?": number; } Is there a ...

Organizing Parsed JSON Data with JavaScript: Using the _.each function for Sorting

var Scriptures = JSON.parse( fs.readFileSync(scriptures.json, 'utf8') ); _.each(Scriptures, function (s, Scripture) { return Scripture; }); This code extracts and displays the names of each book from a collection of scriptures (e.g., Genesis, ...