Async function vs. a function that returns a Promise
Tags: javascript, asyncawait, promise, catch
Published: 2018-10-16 20:01:58

---
title: Async function vs. a function that returns a Promise
published: true
description: 'A small, but quite important difference between a function that returns a Promise and an async function'
tags: javascript, async-await, promise, catch
---

There is a small, but quite important difference between a function that just returns a Promise, and a function that was declared with the `async` keyword.

Take a look at the following snippet:

```javascript
function fn(obj) {
  const someProp = obj.someProp
  return Promise.resolve(someProp)
}

async function asyncFn(obj) {
  const someProp = obj.someProp
  return Promise.resolve(someProp)
}

asyncFn().catch(err => console.error('Catched')) // => 'Catched'
fn().catch(err => console.error('Catched')) // => TypeError: Cannot read property 'someProp' of undefined
```

As you can see, both of the functions above have the same body in which we try to access a property of an argument that is `undefined` in both cases. The only difference between the two functions is that `asyncFn` is declared with the `async` keyword.

This means that Javascript will make sure that the `asnycFn` will return with a Promise (either resolved or rejected) even if an error occured in it, in our case calling our `.catch()` block.

However with the `fn` function the engine doesn't yet know that the function will return a `Promise` and thus it will not call our `catch()` block.

## A more real-world version

I know what you are thinking right now:

> "When the heck will I ever make such a mistake?"

Right?

Well let's create a simple application that does just that.

Let's say we have an express app with MongoDB using MongoDB's Node.JS driver. If you don't trust me I have put all the code on [this github repo](https://github.com/mywebstuff/promise-await-catch), so you can clone and run it locally, but I will also copy-paste all the code here, too.

Here is our `app.js` file:

```javascript
// app.js
'use strict'

const express = require('express')
const db = require('./db')

const userModel = require('./models/user-model')
const app = express()

db.connect()

app.get('/users/:id', (req, res) => {
  return userModel
    .getUserById(req.params.id)
    .then(user => res.json(user))
    .catch(err => res.status(400).json({ error: 'An error occured' }))
})

app.listen(3000, () => console.log('Server is listening'))
```

Take a good look at that `.catch` block in the route definition! That's where the magic will (well not) happen.

The `db.js` file can be used to connect to the mongo database and get the db connection:

```javascript
'use strict'

const MongoClient = require('mongodb').MongoClient

const url = 'mongodb://localhost:27017'
const dbName = 'async-promise-test'

const client = new MongoClient(url)

let db

module.exports = {
  connect() {
    return new Promise((resolve, reject) => {
      client.connect(err => {
        if (err) return reject(err)
        console.log('Connected successfully to server')

        db = client.db(dbName)
        resolve(db)
      })
    })
  },
  getDb() {
    return db
  }
}
```

And finally we have the user model file, which for now only has one function called `getUserById`:

```javascript
// models/user-model.js
'use strict'

const ObjectId = require('mongodb').ObjectId
const db = require('../db')

const collectionName = 'users'

module.exports = {
  /**
   * Get's a user by it's ID
   * @param {string} id The id of the user
   * @returns {Promise<Object>} The user object
   */
  getUserById(id) {
    return db
      .getDb()
      .collection(collectionName)
      .findOne({ _id: new ObjectId(id) })
  }
}
```

If you look back at the `app.js` file you can see that upon visiting the site at the url `localhost:3000/users/<id>` we would call the getUserById function defined in the user-model file, passing in the `id` parameter of the request.

Let's say you visit the following url: `localhost:3000/users/1`. What do you think what will happen?

Well if you answered: "I will get a huge error from the mongo client" - you were right. To be exact you will get an error like this:

```
Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters
```

And what do you think, will this (emphasized via a comment) `.catch` block be called?

```javascript
// app.js

// ... stuff ...

app.get('/users/:id', (req, res) => {
  return userModel
    .getUserById(req.params.id)
    .then(user => res.json(user))
    .catch(err => res.status(400).json({ error: 'An error occured' })) // <=== THIS ONE HERE!
})

// ... stuff ...
```

Nope.

Not by the slightest.

And what would happen if you'd change the function declaration to this?

```javascript
module.exports = {
  // Note that async keyword right there!
  async findById(id) {
    return db
      .getDb()
      .collection(collectionName)
      .findOne({ _id: new ObjectId(id) })
  }
}
```

Yep, you're getting the hang of it. Our `.catch()` block would be called and we would respond to the user with a nice json error.

## Parting thoughts

I hope that for some of you this information was new(ish). Note however, that with this post I'm not trying to get you to always use an `async` function - though they are pretty freakin' awesome. They have their use cases, but they are still just syntactic sugar over the Promises.

I simply wanted you to know, that sometimes being a bit extra careful these Promises can go a long way and when (yeah, not 'if') you will have an error like the one above, you may know where the problem comes from.