7

I'm using Node version 7.6.0 to try out the native async and await features.

I'm trying to figure out why my async call just hanging never actually resolves.

NLP module:

const rest = require('unirest')
const Redis = require('ioredis')
const redis = new Redis()
const Promise = require('bluebird')
const nlp = {}
nlp.queryCache = function(text) {
    return new Promise(function(resolve, reject) {
        redis.get(text, (err, result) => {
            if (err) {
                console.log("Error querying Redis: ", err)
                reject(new Error("Error querying Redis: ", err))
            } else {
                if (result) {
                    let cache = JSON.parse(result)
                    console.log("Found cache in Redis: ", cache)
                    resolve(cache)
                } else {
                    resolve(null)
                }
            }
        })
    })
}

nlp.queryService = function(text) {
    console.log("Querying NLP Service...")
    return new Promise(function(resolve, reject) {
        rest.get('http://localhost:9119?q=' + text)
            .end((response) => {
                redis.set(text, JSON.stringify(text))
                resolve(response.body)
            })
    })
}

nlp.query = async function(text) {
    try {
        console.log("LET'S TRY REDIS FIRST")
        let cache = await nlp.queryCache(text)
        if (cache) {
            return cache
        } else {
            let result = await nlp.queryService(text)
            console.log("Done Querying NLP service: ", result)
            return result
        }
    } catch (e) {
        console.log("Problem querying: ", e)
    }

}
module.exports = nlp

The module consumer:

const modeMenu = require('../ui/service_mode')
const nlp = require('../nlp')
const sess = require('../session')
const onGreetings = async function(req, res, next) {
    let state = sess.getState(req.from.id)
    if (state === 'GREET') {        
        let log = { 
            middleware: "onGreetings"           
        }
        console.log(log)
        let result = await nlp.query(req.text)
        console.log("XXXXXXXX: ", result)
        res.send({reply_id: req.from.id, message: msg})

    } else {
        console.log("This query is not not normal text from user, calling next()")
        next()
    }
};
module.exports = onGreetings;

I'm unable to get the code to proceed to following line:

console.log("XXXXXXXX: ", result) 

I can see that the query was successful in the NLP module

Console log output

Edit: Added console.log statement to response body

Console output on the actual response.body

Log statement

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
Random Joe
  • 640
  • 4
  • 10
  • 25
  • Did you try to put an "async" on all functions that you try to "await" ? And so try to add "await" before "new Promise" too. – Gilsdav Feb 22 '17 at 21:51
  • @Gilsdav - you don't `return await new Promise` - async/await is syntax sugar for Promises – Jaromanda X Feb 22 '17 at 22:41
  • which of the other `console.log` messages are displayed? Your logic seems sound – Jaromanda X Feb 22 '17 at 22:54
  • Hey guys, thanks for looking at this. As you can see @JaromandaX . I'm trying to query the NLP service only after I can't find the it in Redis. The output from the nlp.query() call indicates that the call has returned successfully but it never passes it to the result variable. – Random Joe Feb 22 '17 at 23:05
  • put a `console.log(response.body)` inside `.end((response) => { ... }` - what does that show – Jaromanda X Feb 22 '17 at 23:06
  • I have updated with a couple more output results – Random Joe Feb 22 '17 at 23:19
  • Unless you just ran into a bug, there must be some piece of info missing. The only thing I can suggest is running your app with `node --inspect`, setting a breakpoint in your middleware, and stepping through the execution flow after it gets triggered (remember to check the Async option in the Chrome debugger). – Inkling Feb 22 '17 at 23:25
  • 1
    You don't seem to be handling any errors from `rest.get`, in which case the promise would remain unresolved (and hang your `async function`) indefinitely. – Bergi Feb 22 '17 at 23:36

1 Answers1

9

The most likely cause is an error in a Promise that you aren't catching. I find it helps to avoid try-catch in all but the top calling method, and if a method can be await-ed it almost always should be.

In your case I think the problem is here:

nlp.queryService = function(text) {
    console.log("Querying NLP Service...")
    return new Promise(function(resolve, reject) {
        rest.get('http://localhost:9119?q=' + text)
            .end((response) => {
                redis.set(text, JSON.stringify(text)) // this line is fire and forget
                resolve(response.body)
            })
    })
}

Specifically this line: redis.set(text, JSON.stringify(text)) - that line is calling a function and nothing is catching any error.

The fix is to wrap all your Redis methods in promises, and then always await them:

nlp.setCache = function(key, value) {
    return new Promise(function(resolve, reject) {
        redis.set(key, value, (err, result) => {
            if (err) {
                reject(new Error("Error saving to Redis: ", err));
            } else {
                resolve(result);
            }
        });
    })
}

nlp.queryService = async function(text) {
    console.log("Querying NLP Service...")
    const p = new Promise(function(resolve, reject) {
        rest.get('http://localhost:9119?q=' + text)
            .end((response) => { resolve(response.body) });

        // This is missing error handling - it should reject(new Error... 
        // for any connection errors or any non-20x response status 
    });

    const result = await p;

    // Now any issue saving to Redis will be passed to any try-catch
    await nlp.setCache(text, result);
    return;
}

As a general rule I find it's best practise to:

  • Keep explicit promises low level - have Promise wrapper functions for your rest and redis callbacks.
  • Make sure that your promises reject with new Error when something goes wrong. If a Promise doesn't resolve and doesn't reject then your code stops there.
  • Every call to one of these promise wrappers should have await
  • try-catch right at the top - as long as every Promise is await-ed any error thrown by any of them will end up in the top level catch

Most issues will either be:

  • You have a Promise that can fail to resolve or reject.
  • You call an async function or Promise without await.
Keith
  • 150,284
  • 78
  • 298
  • 434