Search code examples
javascriptnode.jspromiseasync-awaites6-promise

I need a Promise within a Promise and its .then to run first


So, I'm reading this XML file:

<GlDocumentInfo xmlns:opt="Opt.GL.Domain" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="Opt.GL.BusinessLogic">
  <world version="1.0.5">
    <GlDocument key="GlDocument:1">
        <GlDocumentNetwork>
            <Network key="Network:1">
                <Paths>
                    <Path key="Path:1" IsEmpty="False">
                        <PathNodes>
                            <PathNode key="PathNode:1" Node="Node:1" />
                            <PathNode key="PathNode:2" Node="Node:15" Distance="500" />
                            <PathNode key="PathNode:3" Node="Node:13" Distance="320" />
                            <PathNode key="PathNode:4" Node="Node:4" Distance="300" />
                            <PathNode key="PathNode:5" Node="Node:14" Distance="450" />
                        </PathNodes>
                    </Path>
                    <Path key="Path:2" IsEmpty="False">
                        <PathNodes>
                            <PathNode key="PathNode:5" Node="Node:14" />
                            <PathNode key="PathNode:6" Node="Node:4" Distance="450" />
                            <PathNode key="PathNode:7" Node="Node:13" Distance="300" />
                            <PathNode key="PathNode:8" Node="Node:15" Distance="320" />
                            <PathNode key="PathNode:9" Node="Node:1" Distance="500" />
                        </PathNodes>
                    </Path>
                </Paths>
               </Network>
       </GLDocument>
    </world>
</DocumentInfo>

A Path is a schema with this format:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var Path = new Schema({
    key: {type: String, unique: true},
    isEmpty: Boolean,
    pathNodes: [String]
});
module.exports = mongoose.model('Path', Path);

And a PathNode is a schema with this format:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var PathNode = new Schema({
    key: String,
    node: String,
    distance: Number
});
module.exports = mongoose.model('PathNode', PathNode);

So I need to read each PathNode of each Path, create them and save them to the DB, aswell as saving those PathNodes of the current Path in an array, to then create the Path and being able to assign the array to that Path's pathNodes attribute, which is an array of Strings of those PathNodes as you can see. The code I have is:

var PathNode = require('../models/pathNode');
var Path = require('../models/path');
var repository = require('../../repository');

var jsonConverted = JSON.parse(convert.xml2json(req.file.buffer.toString(), { compact: true, spaces: 4, alwaysChildren: true }));
var pathNodesArray = [];

await Promise.all(jsonConverted.GlDocumentInfo.world.GlDocument.GlDocumentNetwork.Network.Paths.Path.map(async path => {

        pathNodesArray = [];
        await Promise.all(path.PathNodes.PathNode.map(async pathNode => {

            var newPathNode = new PathNode();
            newPathNode.key = pathNode._attributes.key;
            newPathNode.node = pathNode._attributes.Node; 
            newPathNode.duration = pathNode._attributes.Duration;
            newPathNode.distance = pathNode._attributes.Distance;

            pathNodesArray.push(newPathNode.key);

            repository.savePathNode(newPathNode);

        })).then(async () => {

            var newPath = new Path();
            newPath.key = path._attributes.key;
            newPath.isEmpty = path._attributes.IsEmpty.toString().toLowerCase();
            Object.assign(newPath.pathNodes, pathNodesArray);

            repository.savePath(newPath);
        });
    })).then(async () => {
(...) //doesn't matter for this question
}

The reading works fine, all PathNodes are being created and saved in the DB just fine, the problem is that Path:1 and Path:2 both are being saved with the same list of PathNodes, Path:1 has the list of PathNodes from Path:2. So this Promise and .then inside the outter Promise is not working properly, because it's reading all PathNodes, and only then it reads the Paths, and assigns the last assembled array of PathNodes to the last Path.

Can you please help me solve this, so each Path has its according PathNodes ? I've tried removing the async from multiple places, await the save... Thank you.


Solution

  • Apart from the issue @Yevhenii mentioned, your var pathNodesArray = []; is in the wrong scope. You want each path to have its own path nodes arrays, and while doing pathNodesArray = []; in the loop might work if it was sequential, it fails when processing your paths in parallel. Prefer const over var:

    const PathNode = require('../models/pathNode');
    const Path = require('../models/path');
    const repository = require('../../repository');
    
    const jsonConverted = JSON.parse(convert.xml2json(req.file.buffer.toString(), { compact: true, spaces: 4, alwaysChildren: true }));
    const paths = jsonConverted.GlDocumentInfo.world.GlDocument.GlDocumentNetwork.Network.Paths.Path;
    await Promise.all(paths.map(async path => {
        const pathNodesArray = [];
    //  ^^^^^ declare inside each iteration
        await Promise.all(path.PathNodes.PathNode.map(async pathNode => {
            const newPathNode = new PathNode({
                key: pathNode._attributes.key,
                node: pathNode._attributes.Node,
                duration: pathNode._attributes.Duration,
                distance: pathNode._attributes.Distance,
            });
            pathNodesArray.push(newPathNode.key);
    
            await repository.savePathNode(newPathNode);
    //      ^^^^^
        }));
    
        const newPath = new Path({
            key: path._attributes.key,
            isEmpty: path._attributes.IsEmpty.toString().toLowerCase(),
            pathNodes: pathNodesArray,
        });
    
        await repository.savePath(newPath);
    //  ^^^^^
    }));
    // … doesn't matter for this question