AKKAweb
AKKAweb

Reputation: 3807

NODE JS: How can I make the following call wait for the called function to finish before proceeding

I still cannot wrap my mind around async/await functionality and I have tried several solutions unsuccessfully for the following scenario:

I have this function:

function addDataToDb(articleData) {

    initMongoDb();

    kanpionDb.on('error', console.error.bind(console, 'connection error:'));
    kanpionDb.once('open', function () {
        SiteUrlModel.init().then(() => {
            articleData.forEach(articleItem => {
                var article = new SiteUrlModel(articleItem).save(function (error) {
                    if (error) {
                        console.log("Failed: " + articleItem.siteName);
                    } else {
                        console.log("Success " + articleItem.siteName);
                    }
                });
            });
        });
    });
}

which is called from within following another file:

c.on('drain', async function () {
    Database.addDataToDb(ArticleList);
    console.log("All done");
});

QUESTION: How can I make console.log("All done") only display the message after Database.addDataToDb(ArticleList) has finished executing?

Upvotes: 0

Views: 67

Answers (3)

jfriend00
jfriend00

Reputation: 707158

This solution is kind of messy because you have a big mixture of asynchronous techniques all mixed in the same function: events, promises and callbacks and then on top of that, there's a loop. That is, indeed, a mess and is generally something you want to avoid.

The best overall solution to this level of complication is to back up a few steps and promisify all the basic asynchronous operations a few steps higher than this. For your database, you should go find and use the promise interface to the database. For your events, since these are both one-shot events you could build a promise wrapper on the object to get notified for the open and error events.

But, since I don't know all the components you're using I can't really do that part of it for you. So, we'll look at what we could do to patch in appropriate promise-level support into this mixture of techniques.

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

function addDataToDb(articleData) {
    return new Promise((resolve, reject) => {
        initMongoDb();

        kanpionDb.on('error', err => {
            console.error('connection error:', error);
            reject(error);
        });
        kanpionDb.once('open', function () {
            SiteUrlModel.init().then(() => {
                return Promise.all(articleData.map(articleItem => {
                    let model = new SiteUrlModel(articleItem);
                    model.savePromise = promisify(model.save);
                    return model.savePromise().then(() => {
                        console.log("Success " + articleItem.siteName);
                    }).catch(err => {
                        console.log("Failed: " + articleItem.siteName);
                        throw err;
                    });
                }));
            }).then(resolve, reject);
        });
    });
}

// usage:
addDataToDb(articleData).then(() => {
    console.log("All Done");
}).catch(err => {
    console.log(err);
});

Summary of techniques:

  1. Because of the events, we will have to wrap in a manually created promise that we resolve or reject when done.
  2. The .forEach() loop gets replaced with Promise.all(articleData.map(...)) which will give us a promise that tells us when all the saves are done.
  3. The .save(callback) has to be replaced with a promisified version of .save(). Ideally, you'd just use a promisified version of your database interface here, but I've shown how to manually promisify the .save() function if necessary.
  4. Hook up reject to the errorevent.
  5. Hook up resolve and reject to the SiteUrlModel.init() promise.
  6. Chain the Promise.all() promise to the SiteUrlModel.init() promise.

Upvotes: 3

Surya
Surya

Reputation: 638

if you could please try this,

const saveData = (articleItem) => {
  return new Promise((resolve, reject) => {
     var article = new SiteUrlModel(articleItem).save(function (error) {
        if (error) {
           console.log("Failed: " + articleItem.siteName);
           reject(error);
        } else {
           console.log("Success " + articleItem.siteName);
           resolve('Success');
        }
     });
  });
});

const addDataToDb = (articleData) => {
  const list = [];
  return new Promise((resolve, reject) => {
    initMongoDb();

    kanpionDb.on('error', console.error.bind(console, 'connection error:'));
    kanpionDb.once('open', function () {
      SiteUrlModel.init().then(() => {
        articleData.forEach(articleItem => {
          list.push(saveData(articleItem);
        });
      });
    });

    Promise.all(list).then(response => {
      resolve(response);
    }).catch(error => {
      reject(error);
    })
  });
};

c.on('drain', async function () {
  const result = Database.addDataToDb(ArticleList).then(res => {
    console.log("All done");
  });
});

I am trying to use the vanilla Promises

Upvotes: 0

Voltaire
Voltaire

Reputation: 93

You can use promises to make the caller wait for your async code to finish. Refactor your addDataToDb function to return a promise and resolve/reject the promise accordingly. Since you've defined the "drain" callback to be an async function, you can use the await keyword inside your callback to make things a bit more concise:

c.on('drain', async function () {
    await Database.addDataToDb(ArticleList);
    console.log("All done");
});

Upvotes: 0

Related Questions