'wait for promise before exit process

I'm trying to run a NodeJS code that reads some data fields from an array, use them to do a database query to check if the data is duplicate before inserting them into the corresponding table.

My NodeJS code will be called from a PHP script so I need to know when it ends this is why I need to add process.exit(0) somewhere. The problem I have is that if I add it, the script is terminated and my promise never gets the time to send back the result.

Here is my code:

var bar = new Promise((resolve, reject) => {
        result.forEach((row, index, array) => {
            var escaped = _.map(row, mysql.escape);
            var checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 = " + escaped[0] + " AND field2 = " + escaped[1] + " AND field3 = " + escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " + escaped[4] + " AND field6 = " + escaped[5] + " AND field7  = " + escaped[6] + ";";
            conn.query(checkQuery, function (err, res) {
                if (err) {
                    console.log("Error checking row for duplicate");
                    console.log(checkQuery);
                    process.exit(1);
                } else {
                    if (res[0].found == 0) {
                        var query = " (";
                        var escaped = _.map(row, mysql.escape);
                        var csv = escaped.join(',');
                        query += csv;
                        query += ")";
                        query += row !== _.last(result) ? ',' : ';';
                        console.log(query);//This will change to inserting the data to the table
                    }else{
                        console.log("Duplicate found!");
                    }
                }
            });
            if (index === array.length -1) resolve();
        });
    });

    bar.then(() => {
        console.log('All done!');
        process.exit(0);
    });

If I remove process.exit(0); I see "All done" first then console.log(query) result. If I add it, the script is terminated and I see "All done" only.

Is there a better approach to do this task please?

Thanks.



Solution 1:[1]

Running multiple asynchronous operations in a loop and tracking when everything is done is just way, way, way easier if you use promises for all the individual asynchronous operation rather than trying to track asynchronous operations that use plain callbacks.

You don't say exactly what your database is, but if it's mysql, then there is a mysql2/promise driver that natively supports promises and that would be my recommendation to switch to that. Then you can directly use a promise returned from .query(). But, without the info about your specific database driver, I've shown how to manually promisify .query().

Then, the looping code can use a for loop and await to sequence the database calls so it's easy to know when they are all complete.

const { promisify } = require('util');

async function someFunc() {
    // other code here

    // promisify conn.query (or use promise interface directly from the database)
    conn.queryP = promisify(conn.query);

    try {
        for (const row of result) {
            const escaped = _.map(row, mysql.escape);
            const checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 = " + escaped[0] + " AND field2 = " +
                escaped[1] + " AND field3 = " + escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " +
                escaped[4] + " AND field6 = " + escaped[5] + " AND field7  = " + escaped[6] + ";";
            let res = await con.queryP(checkQuery);
            if (res[0].found == 0) {
                const csv = _.map(row, mysql.escape).join(',');
                const terminator = row !== _.last(result) ? ',' : ';';
                const query = " (" + csv + ")" + terminator;
                console.log(query); //This will change to inserting the data to the table
            } else {
                console.log("Duplicate found!");
            }

        }
    } catch (e) {
        console.log("Error checking row for duplicate: ", checkQuery);
        console.log(e);
        process.exit(1);
    }
    console.log('All done!');
    process.exit(0);
}

The code appears to be trying to build a query inside the loop where each iteration of the loop will add-on to the next (that's what _.last(result) ? ',' : ';'; look like anyway). If that's the case, then the query variable has to be moved outside the loop so it can build from one iteration of the loop to the next. But, you don't show what you're really trying to do with that query so you're on your own for that part.

Solution 2:[2]

Here is a way to wait for a promise before the application exits.

class Waiter {
  private timeout: any
  constructor() {
    this.waitLoop()
  }
  private waitLoop():void {
    this.timeout = setTimeout(() => { this.waitLoop() }, 100 * 1000)
  }
  okToQuit():void {
    clearTimeout(this.timeout)
  }
}

// Your app.

const appPromise:Promise<any> = ...

const w = new Waiter()
appPromise.finally(() => {
  console.log("Quitting")
  w.okToQuit()
})

Solution 3:[3]

Use Reject/Resolve to manage promise in Nodejs

When your task fulfils your request send result with resolve(); and if its failing use reject();

In your case you are not managing promise properly that's why it's running asynchronously, better to use following way with the proper returns.

var bar = new Promise((resolve, reject) => {
    return result.forEach((row, index, array) => {
        var escaped = _.map(row, mysql.escape);
        var checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 = " + escaped[0] + " AND field2 = " + escaped[1] + " AND field3 = " + escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " + escaped[4] + " AND field6 = " + escaped[5] + " AND field7  = " + escaped[6] + ";";
        return conn.query(checkQuery, function (err, res) {
            if (err) {
                console.log("Error checking row for duplicate");
                console.log(checkQuery);
                return reject(err);
            } else {
                if (res[0].found == 0) {
                    var query = " (";
                    var escaped = _.map(row, mysql.escape);
                    var csv = escaped.join(',');
                    query += csv;
                    query += ")";
                    query += row !== _.last(result) ? ',' : ';';
                    console.log(query);//This will change to inserting the data to the table
                    return resolve(query)
                } else {
                    console.log("Duplicate found!");
                    return reject('Duplicate Found');
                }
            }
        });
    });
});

bar.then((data) => {
    console.log('All done!');
});

In above code I am returning query + resolve/reject so it makes better to run in more synchronised way. return conn.query(checkQuery, function (err, res) {

Plus, while processing this promise I am handling with .then((data) so I can handle that resolve values here.

bar.then((data) => {
    console.log('All done!');
});

Note: If you are rejecting any promise it won't be available in above .then block you'll find this reject in catch block so code will be changed in following way.

bar.then((data) => {
    console.log('All done!');
}).catch(err=>{
    console.log(err);
});

Solution 4:[4]

You can try the following:


(async () => {

    await new Promise((resolve, reject) => {
        result.forEach((row, index, array) => {
            var escaped = _.map(row, mysql.escape);
            var checkQuery = "SELECT COUNT(*) as found FROM data WHERE field1 = 
    " + escaped[0] + " AND field2 = " + escaped[1] + " AND field3 = " + 
escaped[2] + " AND field4 = " + escaped[3] + " AND field5 = " + escaped[4] + " AND field6 = " + escaped[5] + " AND field7  = " + escaped[6] + ";";
            conn.query(checkQuery, function (err, res) {
                if (err) {
                    console.log("Error checking row for duplicate");
                    console.log(checkQuery);
                    process.exit(1);
                } else {
                    if (res[0].found == 0) {
                        var query = " (";
                        var escaped = _.map(row, mysql.escape);
                        var csv = escaped.join(',');
                        query += csv;
                        query += ")";
                        query += row !== _.last(result) ? ',' : ';';
                        console.log(query);//This will change to inserting the data to the table
                    }else{
                        console.log("Duplicate found!");
                    }
                }
            });
            if (index === array.length -1) resolve();
        });
    });


        console.log('All done!');

})();

You don't even need to call the process.exit(0) because the code will always terminate when the job is done :)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 Dharmeshkumar
Solution 4 Saif Ali Khan