异步等待被TypeScript API跳过

时间:2020-01-02 19:02:38

标签: javascript typescript rest

我在设置结果对象的控制器中有一个异步方法。问题在于,不是等待await执行完我的代码,而是跳到响应对象调用,该调用使未定义所需的变量。在调试器中,应该执行的方法中的断点在未定义的错误之后被命中。谁能解释为什么异步等待在这里不起作用?

控制器类中的方法:

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

UserData类:

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) {
                            // We could compare passwords in our model instead of below as well
                            bcrypt.compare(password, user.password).then(match => {
                                if (match) {
                                    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;
                                } 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();

2 个答案:

答案 0 :(得分:2)

请勿将async / await直接与基于回调的API混合使用。

根据the documentationmongoose.connect 确实返回一个诺言(前提是您使用的是最新版本),但是没有任何建议可以使它答应等待您给它的回调中发生的事情。

相反,请在await mongoose.connect之后的代码中执行这些操作。

遵循这些原则(在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;
    }
}

答案 1 :(得分:2)

await在传统的节点样式回调中不起作用。它仅适用于Promises。由于您要提供回调,因此该回调将异步执行,而不会被await等待。

此外,在已经使用await并且函数返回诺言的情况下使用回调确实无法实现甚至使用await的目的。编写没有回调的代码:

        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) {
                // We could compare passwords in our model instead of below as well
                let match = await bcrypt.compare(password, user.password);
                if (match) {
                    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;
                } 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;
        }

对我来说,这更清洁,减少了来自地狱的回调树,并且更优雅地使用了async / await。由于整个内容都包含在try / catch中,因此,如果发生任何await ...错误,则外部catch将捕获该错误并返回带有消息的标准错误500,而无需在其中多次重复该代码。