0

Apologies if I am missing something trivial. What I want to do is to call database queries synchronously. I need to make sure the database inserts are completed before moving to the next step. I also do not want to nest in the callback. I am trying to use promises but it doesn't seem to work as I expected. This is my code:

async init()
{
        await this.initWalletTable();
}


async initWalletTable()
{
   console.log("Creating wallet table");
   var self = this;
   walletParams.wallets.forEach(function(wallet) {

   (async() => {
         await self.insertWallet(wallet); 
         console.log("inserted " + wallet.multisig_wallet_name);
         })();
      });
 }


 insertWallet(wallet)
 {
      console.log("Inserting wallet " + wallet.multisig_wallet_name);
      return new Promise((resolve, reject) => {

            pool.query(`INSERT INTO ${schema}wallet \
                        (name, wallet_name_1, ) \
                        VALUES ($1, $2) \
                        ON CONFLICT (name) DO NOTHING`, [wallet.multisig_wallet_name, wallet.wallet_name1])
            .then(dbres => {
                return resolve(true);
            })
            .catch(e => {
                console.error(e.stack);
               return  resolve(true);
            })

        });
    }
madu
  • 5,232
  • 14
  • 56
  • 96
  • 1
    You can't turn asynchronous database calls into synchronous operations in Javascript. Can't be done. If what you really want to do is "sequence" several operations so the second happens after the first is done, that can be done easily. Please clarify. – jfriend00 Jul 31 '19 at 02:30
  • 1
    FYI, `.forEach()` does not wait for `await`. Use a regular `for` loop if you want the loop to pause for `await`. And, it appears you're misunderstanding `await` too because making an async IIFE with a single `await` in it doesn't really pause anything. – jfriend00 Jul 31 '19 at 02:31
  • Thank you @jfriend00 Yes, I want the DB inserts to complete (the forEach) loop and then go to the next statement. Did not know `forEach` does not wait for `await`. Thank you. I am using `node-postgres` and it seems I can call queries with `await` but even then I am having problems. What is the best way to achieve this? – madu Jul 31 '19 at 02:38

2 Answers2

1

I think the solution I laid out below solves your problem.

What I want to do is to call database queries synchronously. I need to make sure the database inserts are completed before moving to the next step. I also do not want to nest in the callback.

I'm not sure what you are asking. I think I know what you mean, and I tried to solve your problem, but if I am not understanding your intent, please let me know. A couple points for clarification:

  • Promises are designed to make asynchronous code easier to maintain and understand (rather than nesting functions).
  • async / await builds on top of that by making it easy for you to write synchronous looking code
  • this asynchronous looking code still uses promises under the hood, which use callbacks under the hood.
  • You want your methods to be asynchronous, especially since this is database io.

You are asking to have your asynchronous code to run in series, not all at the same time.

Changes Made

  • In insertWallet method, because pool.query() already returns a promise, you do not need to wrap it in an explicit promise.
  • I'm not sure why you wrapped an async anonymous function in an immediately-invoked-function-expression inside initWalletTable(), but I think you will be fine if you remove that block.

Code

class Wallet {
  constructor(walletParams) {
    this.walletParams = walletParams;
    this.initialized = this.init();
  }

  async init() {
    return await this.initWalletTable();
  }

  async initWalletTable() {
    console.log("Creating wallet table");
    this.walletParams.wallets.forEach((wallet) => {
      await this.insertWallet(wallet);
      console.log("inserted " + wallet.multisig_wallet_name);
    });
  }

  async insertWallet(wallet) {
    console.log("Inserting wallet " + wallet.multisig_wallet_name);
    return pool.query(`INSERT INTO ${schema}wallet \
                        (name, wallet_name_1, ) \
                        VALUES ($1, $2) \
                        ON CONFLICT (name) DO NOTHING`, [wallet.multisig_wallet_name, wallet.wallet_name1])
      .catch(e => {
        console.error(e.stack);
        throw e; // re-raise error
      });
  }
}
Community
  • 1
  • 1
FelizNaveedad
  • 358
  • 2
  • 7
  • 1
    The `.forEach()` loop will generate an error since you're attempting to use `await` inside a non-async callback. Even if you mark it `async`, the loop won't pause for the `await` so you'll be doing all the inserts in parallel. Also, there's no reason for `insertWallet()` to be tagged `async`. – jfriend00 Jul 31 '19 at 03:00
  • Thank you @FelizeNaveedad. Yes, I have completely misusndertood the mechanism of promises. I will try this one out. – madu Jul 31 '19 at 03:04
1

Lots of things wrong with your existing code:

  1. You can't turn asynchronous database calls into synchronous operations in Javascript. Can't be done. If what you really want to do is "sequence" several operations so the second happens after the first is done, that can be done easily.

  2. A .forEach() loop does not wait for await. You can use a regular for loop instead.

  3. An async IIFE with just a single await in it, does not really do anything useful. The IIFE still returns immediately (not waiting for the await).

  4. Keep in mind that an async function returns a promise immediately as soon as it hits the first await. It doesn't wait for the async operations to finish before it returns.

  5. You never want to wrap an existing promise in a manually created new Promise(). Instead you just return the promise you already have.

  6. You need an error handling strategy in this code. Right now, it looks like your code tries to just ignore errors. Is that really the right strategy?

You can do this instead:

init() {
    return this.initWalletTable().then(() => {
        console.log("all wallets inserted");
    });
}


async initWalletTable() {
   console.log("Creating wallet table");
   for (let wallet of walletParams.wallets) {
       try {
            await this.insertWallet(wallet);
            console.log("inserted " + wallet.multisig_wallet_name);
       } catch(e) {
           // decide what you're doing if you get an error inserting
            console.log("error inserting " + wallet.multisig_wallet_name);
           throw e;
       }
   }
 }


insertWallet(wallet) {
    return pool.query(`INSERT INTO ${schema}wallet \
                        (name, wallet_name_1, ) \
                        VALUES ($1, $2) \
                        ON CONFLICT (name) DO NOTHING`, [wallet.multisig_wallet_name, wallet.wallet_name1])
        });
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks so much @jfriend00. This works. Yes, I need to have error handling. There's no error handling right now. Can I call the `this.initWallet().then(() => {...` as `await this.initWallet()`? Would that `await` not work there in `init()`? Thank you. – madu Jul 31 '19 at 03:00
  • 1
    @madu - Note that doing some sort of asynchronous initialization in a constructor is a somewhat troublesome issue to design for. The caller needs to know when it's safe to use the new object (when all the initialization is done). You can see a discussion of some of the various options here: [Asynchronous Operations in Constructor](https://stackoverflow.com/questions/49905178/asynchronous-operations-in-constructor/49906064#49906064). – jfriend00 Jul 31 '19 at 03:07
  • 1
    @madu - If your `init()` method is `async`, you can use `await`. I don't generally use `await` when I only have one asynchronous operation, but that's a personal style. Also, make sure you communicate back completion and/or errors properly. – jfriend00 Jul 31 '19 at 03:09