DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Utilizing Database Hooks Like a Pro in Node.js
  • A Beginner's Guide to Back-End Development
  • Nginx + Node.JS: Perform Identification and Authentication
  • Creating a Secure REST API in Node.js

Trending

  • Designing Fault-Tolerant Messaging Workflows Using State Machine Architecture
  • ITBench, Part 1: Next-Gen Benchmarking for IT Automation Evaluation
  • Build a Simple REST API Using Python Flask and SQLite (With Tests)
  • The Truth About AI and Job Loss
  1. DZone
  2. Data Engineering
  3. Databases
  4. How to Interact With a Database Using Async Functions in Node.js

How to Interact With a Database Using Async Functions in Node.js

In the final post of the series on interacting with databases, we'll learn about async functions — the most exciting thing to happen to JavaScript since Ajax.

By 
Dan McGhan user avatar
Dan McGhan
·
Updated Apr. 05, 19 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
182.6K Views

Join the DZone community and get the full member experience.

Join For Free

So far in this async series, we’ve covered Node.js style callbacks, the Async module, and promises. In this final part of the series, we’ll learn about async functions (AKA async/await). To me, async functions are the most exciting thing to happen to JavaScript since Ajax. Finally, we can read JavaScript code in a synchronous manner while it executes asynchronously as it always has.

Async Functions Overview

Async functions are a relatively new feature of JavaScript (not specific to Node.js). Support for the feature first landed in Node.js v7.6 via an update to the V8 JavaScript engine. Because async functions rely heavily on Promises, I recommend you read the previous post before continuing.

I like to think of async functions as two parts: async and await. Let’s look at each part in turn.

async 

For the longest time, we’ve had the ability to create functions in JavaScript using function statements (must be named) or function expressions (often anonymous).

function getNumber() { // Function statment
  return 42;
}

let logNumber = function() { // Function expression
  console.log(getNumber());
}

logNumber(); // 42

If you run the script above in Node.js, you should see 42 printed to the console.

JavaScript now has asynchronous counterparts to these constructs. Placing the new async keyword before the function statement or expression returns an AsyncFunction (async function) object.

async function getNumber() { // Async function statment
  return 42;
}

let logNumber = async function() { // Async function expression
  console.log(getNumber());
}

logNumber(); // Promise { 42 }

Running this script in Node.js should print Promise { 42 }. As you can see, when async functions are invoked, they return promises rather than the actual values returned!

For the async-based script to be the functional equivalent of the first, we’d have to rewrite it as follows.

async function getNumber() { // Async function statment
  return 42;
}

let logNumber = async function() { // Async function expression
  getNumber() // returns a promise
    .then(function(value) {
      console.log(value);
    });
}

logNumber(); // 42

Now we’re back to logging the value 42.

Just as we saw with promise chaining in the previous post, if the async function completes without error, then the promise it returns is resolved. If the function returns a value, then that becomes the promise’s value. If an error is thrown and goes unhandled, then the promise is rejected and the error becomes the promise’s value.

Though interesting, returning promises isn’t what makes async functions special. We could, after all, just return promises from regular functions. What makes async functions special is await.

await 

The await operator, which is only available inside of an async function, is where the magic happens. It’s like hitting the pause button on your code so that it can wait for a promise to be resolved or rejected before continuing. This is a concept known as a coroutine. Coroutines have been available in JavaScript since generator functions were introduced, but async functions make them much more approachable.

Await does not block the main thread. Instead, the currently running call stack, up to the point of await, is allowed to finish so that other functions in the callback queue can be executed. When the promise is resolved or rejected, the remaining portion of the code is queued for execution. If the promise was resolved, its value is returned. If the promise was rejected, the rejected value is thrown on the main thread.

Here’s a demonstration of await that uses setTimeout to simulate an async API. I’ve added some additional console output to help illustrate what’s happening.

function getRandomNumber() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      const randomValue = Math.random();
      const error = randomValue > .8 ? true : false;

      if (error) {
        reject(new Error('Ooops, something broke!'));
      } else {
        resolve(randomValue);
      }
    }, 2000);
  }); 
}

async function logNumber() {
  let number;

  console.log('before await', number);

  number = await getRandomNumber();

  console.log('after await', number);
}

console.log('before async call');

logNumber();

console.log('after async call');

When this script is run in Node.js without an error occurring, the output will look like the following (I’ve added a comment where the two-second delay happens).

before async call
before await undefined
after async call
# 2 second delay
after await 0.22454453163016597

Note that after async call was logged before after await 0.22454453163016597. Only the remaining code in the async function is paused; the remaining synchronous code in call stack will finish.

If an error is thrown, you’ll see the UnhandledPromiseRejectionWarning we covered in the last post. The rejection could be handled with the methods mentioned in that post or using try…catch!

try…catch

In the first post in this series, I explained why try…catch  blocks don’t work with asynchronous operations – you can’t catch errors that occur outside of the current call stack. But now that we have async functions, try…catch can be used for asynchronous operations!

Here’s a stripped down version of the previous script that catches errors that occur in the async API and uses a default value instead.

function getRandomNumber() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      const randomValue = Math.random();
      const error = randomValue > .8 ? true : false;

      if (error) {
        reject(new Error('Ooops, something broke!'));
      } else {
        resolve(randomValue);
      }
    }, 2000);
  }); 
}

async function logNumber() {
  let number;

  try {
    number = await getRandomNumber();
  } catch (err) {
    number = 42;
  }

  console.log(number);
}

logNumber();

If you run that script enough times you’ll eventually get 42 in the output.  try…catch works again, woohoo!

Async Loops

In addition to being able to use try…catch  blocks again, we can do asynchronous loops too! In the following example, I use a simple for loop that logs out three values serially.

function getRandomNumber() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      const randomValue = Math.random();
      const error = randomValue > .8 ? true : false;

      if (error) {
        reject(new Error('Ooops, something broke!'));
      } else {
        resolve(randomValue);
      }
    }, 2000);
  }); 
}

async function logNumbers() {
  for (let x = 0; x < 3; x += 1) {
    console.log(await getRandomNumber());
  }
}

logNumbers();

Running this script in Node.js, you should see three numbers printed to the console every two seconds. No third party libraries, no complicated promise chains, just a simple loop. Loops work again, yay!

Parallel Execution

Clearly, async functions make it easy to do sequential flows and use standard JavaScript constructs with asynchronous operations. But what about parallel flows? This is where Promise.all and Promise.race come in handy. Because they both return promises, await can work with them like any other promise-based API.

Here’s an example that uses Promise.all to get three random numbers in parallel.

function getRandomNumber() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      const randomValue = Math.random();
      const error = randomValue > .8 ? true : false;

      if (error) {
        reject(new Error('Ooops, something broke!'));
      } else {
        resolve(randomValue);
      }
    }, 2000);
  }); 
}

async function logNumbers() {
  let promises = [];

  promises[0] = getRandomNumber();
  promises[1] = getRandomNumber();
  promises[2] = getRandomNumber();

  Promise.all(promises)
    .then(function(values) {
      console.log(values);
    })
    .catch(function(err) {
      console.log(err);
    });
}

logNumbers();

Because Promise.all rejects its promise if any promise passed in is rejected, you may need to run the script a few times to see the three random numbers printed out.

Async Function Demo App

The async function demo app is comprised of the following four files. The files are also available via this Gist.

package.json:

{
  "name": "async-functions",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "Dan McGhan <dan.mcghan@oracle.com> (https://jsao.io/)",
  "license": "ISC",
  "dependencies": {
    "oracledb": "^1.13.1"
  }
}

This is a very basic package.json file. The only external dependency is oracledb.

index.js:

const oracledb = require('oracledb');        
const dbConfig = require('./db-config.js');      
const employees = require('./employees.js');

async function startApp() {
  try {
    await oracledb.createPool(dbConfig);

    let emp = await employees.getEmployee(101);

    console.log(emp);
  } catch (err) {
    console.log('Opps, an error occurred', err);
  }
}

startApp();

All of the async methods in node-oracledb are overloaded to work with callback functions or promises. If a callback function is not passed in as the last parameter, then a promise will be returned. This version of the index.js uses the await operator with the driver’s promise APIs to create a connection pool and fetch an employee. Although the pool is returned from the call to createPool, it’s not referenced here as the built-in pool cache will be used in employees.js.

db-config.js:

module.exports = {
  user: 'hr',
  password: 'oracle',
  connectString: 'localhost:1521/orcl',
  poolMax: 20,
  poolMin: 20,
  poolIncrement: 0
};

The db-config.js file is used in index.js to provide the connection info for the database. This configuration should work with the DB App Dev VM, but it will need to be adjusted for other environments.

employees.js:

const oracledb = require('oracledb');

function getEmployee(empId) {
  return new Promise(async function(resolve, reject) {
    let conn; // Declared here for scoping purposes.

    try {
      conn = await oracledb.getConnection();

      console.log('Connected to database');

      let result = await conn.execute(
        `select *
        from employees
        where employee_id = :emp_id`,
        [empId],
        {
          outFormat: oracledb.OBJECT
        }
      );

      console.log('Query executed');

      resolve(result.rows[0]);
    } catch (err) {
      console.log('Error occurred', err);

      reject(err);
    } finally {
      // If conn assignment worked, need to close.
      if (conn) {
        try {
          await conn.close();

          console.log('Connection closed');
        } catch (err) {
          console.log('Error closing connection', err);
        }
      }
    }
  });
}

module.exports.getEmployee = getEmployee;

This version of the employees module is similar to the promise version in that the getEmployee function was written as a promise-based API — it immediately returns a new promise instance that is asynchronously resolved or rejected. The main difference is that await is used with the driver’s promise APIs to get a connection to the database, use it to execute a query, and then close a connection.

A try…catch…finally block was used to catch errors and ensure the connection was closed either way. To me, this version of the module is the simplest to read of all those in the series and it doesn’t hurt that it has the fewest lines of code as well.

Hopefully, you now have a better grasp on async functions and are as excited as I am about using them!

Node.js Database

Published at DZone with permission of Dan McGhan, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Utilizing Database Hooks Like a Pro in Node.js
  • A Beginner's Guide to Back-End Development
  • Nginx + Node.JS: Perform Identification and Authentication
  • Creating a Secure REST API in Node.js

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!