使用 Javascript(以及 Node.js)使用 async/await 和 Promises 的正确方法是啥 [重复]

Posted

技术标签:

【中文标题】使用 Javascript(以及 Node.js)使用 async/await 和 Promises 的正确方法是啥 [重复]【英文标题】:What's the correct way to use async/await and Promises with Javascript (and therefore Node.js) [duplicate]使用 Javascript(以及 Node.js)使用 async/await 和 Promises 的正确方法是什么 [重复] 【发布时间】:2021-11-15 19:06:14 【问题描述】:

我实际上正在构建一个脚本来从 mysql 数据库中提取数据,然后填充 MongoDB。在这个过程中,有一些异步的东西,比如建立与 MySQL(通过 Sequelize 库)和 MongoDB(通过 Mongoose 库)的连接,还有一些同步的东西,比如获取和转换数据。

我阅读了很多关于 async/await 和 Promises 的内容,我的脚本在全球范围内执行我想要的操作,但仍然存在一些问题。


代码如下:


Migration.class.mjs

import MigrationBase from './Base/MigrationBase.class.mjs';

export default class Migration extends MigrationBase

    constructor(config) 
        super(config);

        this.mysqlData = ;
        this.mongoData = ;
    

    async run() 
        await this.selectMySQLData();
        let docs = await this.convertMySQLToMongo();
        await this.checkConvertedData(docs);
        await this.insertMongoData();
    

    async selectMySQLData() 
        return new Promise(async resolve => 
            await this.runSequelize();
            console.log('B - Grabbing MySQL data\n');

            for(var key in this.mysqlModels) 
                if (this.mysqlModels.hasOwnProperty(key)) 
                    let search =  raw: true ;
                    this.mysqlData[key] = await this.mysqlModels[key].findAll(search);
                
            

            await this.closeSequelize();
            resolve();
        );
    ;

    convertMySQLToMongo() 
        return new Promise(async resolve => 
            console.log('D - Convert MySQL data to MongoDB\n');

            let customersDocument = this.defaultDocuments.customers;
            let personalInfosDocument = this.defaultDocuments.personal_infos;
            let billingInfosDocument = this.defaultDocuments.billing_infos;
            // ... etc ...

            await Object.entries(this.mysqlData.customer).forEach(async keyRow => 
                let [key, row] = keyRow;

                await Object.entries(row).forEach(async keyValue => 
                    customersDocument = await this._processCustomersFields(customersDocument, 'Customer', keyValue);
                    personalInfosDocument = await this._processPersonalInfosFields(personalInfosDocument, 'PersonalInfo', keyValue);
                    billingInfosDocument = await this._processBillingInfosFields(billingInfosDocument, 'BillingInfo', keyValue);
                    // ... etc ...
                    
            );

            resolve([
                customersDocument,
                personalInfosDocument,
                billingInfosDocument,
                // ... etc ...
            ]);
        );
    ;

    checkConvertedData([
        customersDocument,
        personalInfosDocument,
        billingInfosDocument,
        // ... etc ...
    ]) 
        return new Promise(resolve => 
            console.log('E - Checking converted data');

            if (! this._isNull(customersDocument, 'Customers')) 
                this.mongoData.customers = customersDocument;
            
            
            if (! this._isNull(personalInfosDocument, 'PersonalInfos')) 
                this.mongoData.personal_infos = personalInfosDocument;
            
            
            if (! this._isNull(billingInfosDocument, 'BillingInfos')) 
            
               this.mongoData.billing_infos = billingInfosDocument;
            // ... etc ...
            
            resolve();
        );
    

    async insertMongoData() 
        return new Promise(async resolve => 
            await this.runMongoose();
            console.log('G - Insert MongoDB data.');
            
            await this.mongoModels.customers.create(this.mongoData.customers);
            await this.mongoModels.personal_infos.create(this.mongoData.personal_infos);
            await this.mongoModels.billing_infos.create(this.mongoData.billing_infos);
            // ... etc ...

            await this.closeMongoose();
            resolve();
        );
    ;

    _processCustomersFields(defaultDoc, docName, [colName, val]) 
        return new Promise(resolve => 
            switch (colName) 
                case 'id_customer':
                    console.log(`$docName: $colName => $val`);
                    defaultDoc.id = val;
                    break;
                case 'email_customer':
                    console.log(`$docName: $colName => $val`);
                    defaultDoc.email = val;
                    break;
                case 'password_customer':
                    console.log(`$docName: $colName => $val`);
                    defaultDoc.password = val;
                    break;
                // ... etc ...
            
    
            resolve(defaultDoc);
        );
    

    _processPersonalInfosFields(defaultDoc, docName, [colName, val]) 
        return new Promise(resolve => 
            switch (colName) 
                // ... Same kind of code as in _processCustomersFields() ...
            
            
            resolve(defaultDoc);
        );
    

    _processBillingInfosFields(defaultDoc, docName, [colName, val]) 
        return new Promise(resolve => 
            switch (colName) 
                // ... Same kind of code as in _processCustomersFields() ...
            
            
            resolve(defaultDoc);
        );
    
    
    _isNull(document, mongoName) 
        if (document !== null) 
            console.log(`\n$mongoName:\n`, JSON.stringify(document));
            return false;
         else 
            console.log(`Error processing \`$mongoName\` data!`);
            return true;
        
    
    
    _valueExists(val) 
        return (val !== null && val !== "" && typeof val !== "undefined")
            ? true
            : false
        ;
    


MigrationBase.class.mjs

import Sequelize from 'sequelize';
import DataTypes from 'sequelize';
import Mongoose from 'mongoose';
import Crypto from 'crypto';
import Models from '../../../models.mjs';
import Schemas from '../../../schemas.mjs';

export default class MigrationBase

    constructor(config) 
        this.config = config;
        this.sequelize = this.createSequelize();
        this.mongoose = Mongoose;
        this.defaultDocuments = this.initDefaultDocuments();
        this.mysqlModels = this.initMysqlModels();
        this.mongoModels = this.initMongoSchemas();
        this.mysqlData = ;
        this.mongoData = ;
    

    createSequelize() 
        return new Sequelize(
            this.config.mysql.dbName,
            this.config.mysql.dbUser,
            this.config.mysql.dbPass,
            this.config.sequelize
        );
    

    initDefaultDocuments() 
        const defaultDocument = 
            "deleted_at": 0 // Thu Jan 01 1970 01:00:00 GMT+0100
        ;

        let defaultDocuments = 
            "customers": Object.assign(, defaultDocument),
            "personal_infos": Object.assign(, defaultDocument),
            "billing_infos": Object.assign(, defaultDocument)
            // ... etc ...
        ;

        return defaultDocuments;
    

    initMysqlModels() 
        return 
            "customer": Models.Customer(this.sequelize, DataTypes),
            "billing_address": Models.BillingAddress(this.sequelize, DataTypes),
            // ... etc ...
        ;
    

    initMongoSchemas() 
        return 
            "customers": this.mongoose.model('Customer', Schemas.Customers),
            "personal_infos": this.mongoose.model('PersonalInfo', Schemas.PersonalInfos),
            "billing_infos": this.mongoose.model('BillingInfo', Schemas.BillingInfos),
            // ... etc ...
        
    

    async runSequelize() 
        console.log('A - Connection to MySQL');

        try 
            await this.sequelize.authenticate();
            console.log('Connection to MySQL has been established successfully.\n');
         catch (err) 
            console.error("Unable to connect to the MySQL database:", err + '\n');
        
    

    async closeSequelize() 
        console.log('C - Closing MySQL connection.\n');

        await this.sequelize.close();
    ;

    runMongoose() 
        return new Promise(async resolve => 
            console.log('F - Connection to MongoDB');

            try 
                await this.mongoose.connect(
                    `mongodb://$this.config.mongo.dbHost:$this.config.mongo.dbPort/$this.config.mongo.dbName`,
                     useNewUrlParser: true, useUnifiedTopology: true 
                );

                console.log('Connection to MongoDB has been established successfully.');
             catch (err) 
                console.error('Unable to connect to the MongoDB database: ', err);
            

            resolve();
        );
    

    async closeMongoose() 
        console.log('H - Closing MongoDB connection.');
        await this.mongoose.connection.close();
    ;


这是日志输出:

A - Connection to MySQL
Connection to MySQL has been established successfully.

B - Grabbing MySQL data

C - Closing MySQL connection.

D - Convert MySQL data to MongoDB

Customer: id_customer => 1
Customer: email_customer => contact@example.com
Customer: password_customer => 0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d
// ... etc ...
PersonalInfo: id_customer => 1
PersonalInfo: lastname_customer => Doe
PersonalInfo: firstname_customer => John
// ... etc ...
E - Checking converted data

Customers:
 "deleted_at":0,"id":"000000000000000000000001","email":"contact@example.com","password":"0a1b2c3d4e5f0a1b2c3d4e5f0a1b2c3d", ... etc ... 

PersonalInfos:
 "deleted_at":0,"customer_id":"000000000000000000000001","last_name":"Doe","first_name":"John", ... etc ... 

BillingInfos:
 "deleted_at":0
BillingInfos: id_customer => 1
BillingInfo: company => ExampleCompany
F - Connection to MongoDB
BillingInfos: lastname => Doe
BillingInfo: firstname => John
Connection to MongoDB has been established successfully.
G - Insert MongoDB data.
/home/user/Workspaces/namespace/project-name/node_modules/mongoose/lib/document.js:2757
    this.$__.validationError = new ValidationError(this);
                               ^

ValidationError: BillingInfos validation failed: id_customer: Cast to ObjectId failed for value "1" (type number) at path "customer_id", values: Path `values` is required., id: Path `id` is required.

这里我们可以看到正确的顺序:

A - Connection to MySQL

B - Grabbing MySQL data

C - Closing MySQL connection

D - Convert MySQL data to MongoDB

然后我们可以看到E - Checking converted data,但是转换过程还没有完成,尽管有等待语句并且它返回了一个Promise。

之后我们还可以看到BillingInfos: id_customer => 1BillingInfo: company => ExampleCompany表示转换过程仍在循环中做事。

然后F - Connection to MongoDB

然后另一个转换记录 BillingInfos: lastname => DoeBillingInfo: firstname => John(转换过程仍在循环中进行)。

然后G - Insert MongoDB data.

最后是验证错误,因为某些 Mongo 文档不完整,因此规则未完整归档。


问题?

所以问题是我在这里做错了什么?

正如我所说,我阅读了很多关于 async/await 和 Promises 的内容,但仍然难以理解为什么它不起作用。

提前致谢,如果您需要更多信息,请告诉我。

【问题讨论】:

【参考方案1】:

那是因为 await will not work inside forEach(),您正试图在 convertMySQLToMongo() 函数中执行此操作。

有很多方法可以解决,其中一种方法是使用for ... of 而不是forEach()

for (const keyRow of Object.entries(this.mysqlData.customer)) 
    let [key, row] = keyRow;
  
    for (const keyValue of Object.entries(row)) 
        customersDocument = await this._processCustomersFields(customersDocument, 'Customer', keyValue);
        personalInfosDocument = await this._processPersonalInfosFields(personalInfosDocument, 'PersonalInfo', keyValue);
        billingInfosDocument = await this._processBillingInfosFields(billingInfosDocument, 'BillingInfo', keyValue);
    

【讨论】:

是的,它有效!我现在明白我的错误了。对我来说,引擎盖下发生了什么变得越来越清楚。此外,我应该阅读更多关于“forEach”文档,尤其是蓝色“Notes”的信息:MDN Reference我想从现在开始我肯定会禁止“forEach”并且只使用它的非异步替代品,如for、@987654328 @ 或 for ... of 按照您的建议。非常感谢!

以上是关于使用 Javascript(以及 Node.js)使用 async/await 和 Promises 的正确方法是啥 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

Node.js简介以及环境配置

72.vue开发工具node.js以及构建工具webpack

在 V8 JavaScript (Chrome & Node.js) 中访问行号

Node.js的简介以及安装环境

导语3——node以及npm

node.js 概述与安装以及环境搭建