3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Expressでawaitしているのに非同期処理されてしまう

Last updated at Posted at 2019-09-03

#現状
ログイン処理の作成中、postされたユーザ情報を元にDBベースに検索をかけてパスワードのチェックをして結果を返すはずが、結果を待たずundefinedを返してしまいます。

#問題のコード
まずは、Expressのポストを受けつける部分です。

index.js
    app.post('/signup', async (req, res, next) => {
        try {
            var controler = new AccountControler(dbcontroler);
            var result = await controler.signup(req.body);
            logging.logger.debug(result);
            res.json(result);
        } catch(error) {
            next(error)
        }
    });

ログイン処理を行うAccountControlerのsignupメソッドをawaitでコールしています。
また、その結果をコンソールにログとして吐き出しています。

AccountControler.js
    async getLoginSuccessMessage(res) {
        return {
            success: true,
            token: await jwt.sign(
                {
                    userid: res.userid,
                    authtime: moment().format()
                }, 
                publickey
            ),
            userinfo: {
                username: res.username
            }
        }
    }

    async signup(data) {

        try {
            data = JSON.parse(JSON.stringify(data));
            // DBからユーザ情報を取得
            var res =  await this.dbcontroler.selectRecode(
                this.LoginUserViewModel.getSelectSqlOfLoginUser(data.loginuser.loginid),
                (res) => this.LoginUserViewModel.serializerFromDatabase(res));

            if (res.length == 0) {
                return this.getLoginFailedMessage();
            } else {
                // パスワードが一致しているかチェック
                return await bcrypt.compare(data.loginuser.loginpassword, res[0].loginpassword, async (err, result) => {
                    if (err) {
                        this.logger.debug(err);
                        return await this.getLoginFailedMessage();
                    } else if (result) {
             // ログ出力
                        this.logger.debug('result is' + JSON.stringify(await this.getLoginSuccessMessage(res)));
                        // ログイン可能時に返却するデータを生成
                        return await this.getLoginSuccessMessage(res);
                    } else {
                        return await this.getLoginFailedMessage();
                    }
                })
            }

        } catch (error) {
            this.logger.debug(error);
            throw error;
        }
    }

いくつか処理を行っていますが、
①ログインIDをキーにDBからユーザ情報を取得(コールバック使用)
②bcryptでハッシュ化されたパスワードと受信したパスワードを比較(コールバック使用)
③結果をjwtでユーザ情報に関する情報を暗号化

上記のような感じで全てawaitを使用しています。

ログ
[2019-09-03T10:03:09.072] [DEBUG] default - undefined
[2019-09-03T10:03:09.216] [DEBUG] default - result is{"success":true,"token":"eyJhbGcU...

長いので少し省略していますが、分かりにくいログですが、1行目が最初のコードのログ出力で、次が2つめのコードのログ出力内容です。
最初のコードでawaitが待ってくれず、処理されているように思えます。

#修正内容1
一応Expressが同期処理に対応しているかの確認も含めて、以下をチェックしました

Express公式:Use promises

とりあえず、promise形式に変更してみました。

index.js
    app.post('/signup', (req, res, next) => {
        var controler = new AccountControler(dbcontroler);
        controler.signup(req.body)
        .then((d) => {
            logging.logger.debug(d);
            res.json(d)
        })
        .catch((error) => next(error));
    });

結果はかわらず

#修正内容2
上記の変更はのままに、コール側の処理も合わせてPromiseに変更しました

AccountControler.js
    async signup(data) {

        return new Promise((resolve, reject) => {
            data = JSON.parse(JSON.stringify(data));
    
            this.logger.debug(JSON.stringify(data))

            this.dbcontroler.selectRecode(
                this.LoginUserViewModel.getSelectSqlOfLoginUser(data.loginuser.loginid),
                (res) => this.LoginUserViewModel.serializerFromDatabase(res))
            .then((res) => {
                if (res.length == 0) {
                    return this.getLoginFailedMessage();
                } else {
                    bcrypt.compare(data.loginuser.loginpassword, res[0].loginpassword)
                    .then( async (result) => {
                        if (result) {
                            this.logger.debug('result is' + JSON.stringify(await this.getLoginSuccessMessage(res)));
                            resolve(await this.getLoginSuccessMessage(res));
                        } else {
                            resolve(await this.getLoginFailedMessage());
                        }

                    })
                    .catch((error) => {
                        this.logger.debug(error);
                        reject(error)
                    })
                    
                }
    
            })
            .catch((error) =>{ 
                this.logger.debug(error);
                reject(error);
            })
        });
    }

結果、うまく動作しました。ログは下記の通りです。

ログ
[2019-09-03T11:45:41.723] [DEBUG] default - result is{"success":true,"token":"eyJhbGciOiJ...
[2019-09-03T11:45:41.724] [DEBUG] default - { success: true,token:'eyJhb...

#結論
最終的に期待通りに動作はしました。しかし、なぜ動作したのか解っていません。
(私のasync/awaitの使い方が悪いということかも知れません。多分そんなことだと思いますが…)
解るのは、他も全部修正ということだけです…

ちなみに私は、下記を参考にプロジェクトを生成しています。
Vue-CLI3 ベースのアプリ開発で JSON API が使える express server を使う

簡単に作成方法を記載すると、

$vue create prpjectname
$vue add express

上記でvue-cli-plugin-expressがインストールされるようです。

バージョンは下記の通りです。

$npm view express version
4.17.1
$npm view vue-cli-plugin-express version
1.0.2

バージョンは現時点では最新のようです。
vue-cliを用いずExpressを使用した場合、このような動作を行うか分かりません。
残念ながら試すのも困難(面倒…)なのでここまでです。

この方法がベスト(スタンダード?)なのか、どうなのか気になります。
知っている方はコメントして頂けると、私の気持ちがスッキリするのでコメントしてください。

#再修正
コメント頂きました!ありがとうございます。

ということで、大本のソースの状態に戻した上で、下記のように修正を行いました。

AccountControler.js
    async signup(data) {


        try {
            data = JSON.parse(JSON.stringify(data));

            this.logger.debug(JSON.stringify(data))

            var res =  await this.dbcontroler.selectRecode(
                this.LoginUserViewModel.getSelectSqlOfLoginUser(data.loginuser.loginid),
                (res) => this.LoginUserViewModel.serializerFromDatabase(res));

            if (res.length == 0) {
                return this.getLoginFailedMessage();
            } else {
                // 修正は以下の部分でcallbackを取りやめました
                const result = await bcrypt.compare(data.loginuser.loginpassword, res[0].loginpassword);

                if (result) {
                    this.logger.debug('result is' + JSON.stringify(await this.getLoginSuccessMessage(res)));
                    return await this.getLoginSuccessMessage(res);
                } else {
                    return await this.getLoginFailedMessage();
                }

            }

        } catch (error) {
            this.logger.debug(error);
            throw error;
        }
    }

上記の結果、期待通りに動作致しました!

コメント頂いた下記からの抜粋です。
bcrypt

Async methods that accept a callback, return a Promise when callback is not specified if Promise support is available.

callbackを指定しなければ、Promiseを返すと書かれていますね。
やはり、私の書き方が悪かったようです。でも、スッキリしました。

3
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?