3
votes

The event handler for websocket connection (wss.on) is called that calls an async function sendInitialState(). There I call an async function (getMetadata()) that returns a Promise in which I do some async stuff. I resolve the promise, resolve(res) is called, but the program still blocks on await, that is console.log(metadata) is never called.

wss.on('connection', (ws) => {

    sendInitialState();
});

const sendInitialState = async (ws) => {

    console.log('Sending initial state!');
    const metadata = await getMetadata();
    console.log('metadata:', metadata);
};

const getMetadata = async () => {

    return new Promise((resolve, reject) => {
        MongoClient.connect(url, (err, db) => {
            if (err) {reject(err);}
            else {
              db.collection("meta").findOne({type: 'a'}, (err, res) => {
                if (err) reject(err);
                else {
                    db.close();
                    console.log('Metadata found: ', res);
                    resolve(res);
                }

              });
            }
        }); 
    });

}

What is the reason my program blocks?

(I tried adding async before outer (ws) =>, but outcome is the same)

1
For starters, if (err) throw err is wrong and should be if (err) return reject(err); in both places you have it. For future reference, if (err) throw err should pretty much NEVER be in your code, ever. I've never encountered a circumstance where that is correct error handling. And, then you need to put a try/catch around your await so you can catch any errors that might happen. Error handling is not to be ignored here and is probably the first thing you should look at when things are not working as expected.jfriend00
Also, it should be console.log('metadata:', blockchainMetadata);. You're referring to the wrong variable in your log statement.jfriend00
@jfriend00 Promises automatically convert any thrown errors to a rejected Promise that can be caught in a catch(). Why is throw in this case invalid, isn't this just a performance thing since there isn't optimization around throw?peteb
@peteb - That code is not inside a promise callback. It's inside a database async callback. There's no auto-conversion to reject there.jfriend00
No. reject() is JUST an ordinary function call. Your function keeps executing the rest of the code in the block. You should use regular flow control to separate out the code you want to execute in the error path from the non-error path. Regular Javascript programming techniques.jfriend00

1 Answers

1
votes

While reject()'ing the promise instead of a throw solved it for you, your code style can be improved:

  • When converting a callback to a promise, there's no need to declare the function as async: it would just wrap into another promise;
  • It is probably better to declare functions instead of assigning a function expression to a const;
  • To reduce indentation, the pattern would be if (err) { return reject(err); }: this removes the need for an else clause;
  • We probably want to reuse promise-based functions, so extracting common logic leads to more readable code;

The OP was more about promises than async/await, so this is a rewritten version with smaller functions, shallow indentation and using more of async/await.

wss.on('connection', (ws) => {
  sendInitialState();
});

async function sendInitialState(ws) {
  console.log('Sending initial state!');
  const metadata = await getMetadata();
  console.log('metadata:', metadata);
}

function connect(url) {
  return new Promise((resolve, reject) => {
    MongoClient.connect(url, (err, db) => {
      if (err) {
        return reject(err);
      }
      resolve(db);
    });
  });
}

function findOne(db, collectionName, object) {
  return new Promise((resolve, reject) => {
    db.collection(collectionName).findOne(object, (err, res) => {
      if (err) {
        return reject(err);
      }
      console.log('Metadata found: ', res);
      resolve(res);
    });
  });
}

async function getMetadata() {
  const db = await connect(url);
  const res = await findOne(db, 'meta', {
    type: 'a'
  });
  db.close();
  console.log('Metadata found: ', res);
  return res;
}