1

I was trying to get my hands dirty on advanced NodeJS concepts by Stephen Grinder.

Trying to teach the mere basics of redis, Stephen did something like this

app.get('/api/blogs', requireLogin, async (req, res) => {

    //This time we are setting 
    const redis = require('redis')
    const redisURL = 'redis://127.0.0.1:6379';
    const  client = redis.createClient(redisURL);
    const util = require('util')
    client.get = util.promisify(client.get)
    //We are checking if we have ever fetched any blogs related to the user with  req.user.id
    const cachedBlog = await client.get(req.user.id) 
    //if we have stored list of blogs, we will return those 
    if (cachedBlog) {
      console.log(cachedBlog)
      console.log("Serving from Cache")
    return res.send(JSON.parse(cachedBlogs))
    } //this is JSONIFIED as well so we need to convert it into list of arrays

    console.log("serving from Mongoose")
    //if no cache exsist 
    const blogs = await Blog.find({_user: req.user.id})
    //blogs here is an object so we would need to stringfy it 
    res.send(blogs);
  client.set(req.user.id, JSON.stringify(blogs))

  })

And it works without any error but in last two lines, if we change the order

 client.set(req.user.id, JSON.stringify(blogs))
 res.send(blogs);

it does not display my blog.

Since inside the API, I am considering both of them to run asynchronously, I thought order won't matter.

Can anyone tell me what am I missing or unable to comprehend?

Alwaysblue
  • 9,948
  • 38
  • 121
  • 210

3 Answers3

2

The order of these two lines doesn't matter but that res.send isn't called in case client.set goes first means that there's an error. If an error occurs in async function, this may result in UnhandledPromiseRejectionWarning warning that will be visible in console.

There are several problems with this snippet.

That the error occurs even when though client.set is asynchronous suggests that client.set causes synchronous error which wasn't caught.

client.set wasn't promisified but it should for correct control flow. That it wasn't provided with callback argument could be a reason why it caused an error.

As explained in this answer, Express doesn't support promises, all rejections should be explicitly handled for proper error handling.

All common code like require goes outside middleware function. It should be:

const redis = require('redis')
const redisURL = 'redis://127.0.0.1:6379';
const  client = redis.createClient(redisURL);
const util = require('util')
client.get = util.promisify(client.get)
client.set = util.promisify(client.set)

app.get('/api/blogs', requireLogin, async (req, res, next) => {
  try {
    const cachedBlog = await client.get(req.user.id) 

    if (cachedBlog) {
      return res.send(JSON.parse(cachedBlogs))
    }

    const blogs = await Blog.find({_user: req.user.id});
    await client.set(req.user.id, JSON.stringify(blogs));
    res.send(blogs);
  } catch (err) {
    next(err);
  }
})

Most popular libraries have promise counterparts that allow to skip boilerplate promisification code, this applies to redis as well.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
2

Since OP asks to understand the difference, not fix the code:

express runs the request handler function and catches synchronous errors (they become http500 errors). It doesn't do anything with the promise returned from the async function and doesn't use await internally, so you don't get error handling for async functions for free. All asynchronous errors need to be caught inside and passed to the next callback or handled in your code by sending an appropriate status code and error body.

When an error occurs, JS stops and doesn't execute any more lines in the function. So if an error is thrown from client.set placed before res.send, the line with send won't run and no response is sent. The browser should continue waiting for the response until timeout.

The other way around - you send response before the error, so you get the page, but the response doesn't end (I'd assume the connection remains open as if the backend was going to send more) but ever since early versions of Firefox browsers start rendering HTML as it's downloaded, so you see a page even though the browser is still waiting for the response to finish.

naugtur
  • 16,827
  • 5
  • 70
  • 113
1

The two task will runs asynchronously but the order of execution matters.

client.set(req.user.id, JSON.stringify(blogs)) execution starts first, but as you are not using await, the promise will not be resolved but execution has already started. After that res.send() will execute.

You are not getting the response implies that there is some error in the execution of client.set(req.user.id, JSON.stringify(blogs)).

Use Try catch block to trace this error (as mentioned in other answers).

You can also add these lines in your code to catch other "unhandledRejection" or "uncaughtException" error (if any).

process.on('unhandledRejection', (err) => {
    logger.error('An unhandledRejection error occurred!');
    logger.error(err.stack)
});
process.on('uncaughtException', function (err) {
    logger.error('An uncaught error occurred!');
    logger.error(err.stack);
});
Mukul Dev
  • 196
  • 8