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

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

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

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

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

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

  • DZone's Article Submission Guidelines
  • How to Submit a Post to DZone
  • Using Python Libraries in Java
  • Tired of Spring Overhead? Try Dropwizard for Your Next Java Microservice
  1. DZone
  2. Data Engineering
  3. Databases
  4. How to Interact With a Database Using Promises in Node.js

How to Interact With a Database Using Promises in Node.js

Understanding promises is essential to understanding async functions. Learn the basics of promises and demonstrate how they can be used to construct asynchronous apps.

By 
Dan McGhan user avatar
Dan McGhan
·
Aug. 02, 17 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
67.9K Views

Join the DZone community and get the full member experience.

Join For Free

The first two patterns we looked at in this series were Node.js callbacks and the Async module. While effective, those patterns are not the only ways of writing asynchronous code. Patterns built around concepts such as “deferreds” and “promises” were first introduced to the JavaScript community via third-party libraries like Q and Bluebird. Over time a native implementation of promises was added to ECMAScript, then implemented in V8, and later integrated into Node.js.

Promises are important to learn because many modules use them in their APIs. Also, they are integral to async functions, which I’ll cover in the next part of the series. This post will cover the basics of promises and demonstrate how they can be used to construct asynchronous applications.

Promise Overview

Native support for promises was added to JavaScript via the Promise object. The Promise object is a constructor function that’s used to create new promise instances. Promise instances have two important properties: state and value. Let’s take a look at how we can manipulate those values and respond to state changes.

resolve and reject 

When a new promise is created, the constructor function accepts a “resolver” function that should have two formal parameters: resolve and reject. When the resolver function is executed, which happens immediately when creating new instances, these parameters will be functions that can transition the state of the promise and provide a value. Promises are typically resolved when the resolver completes successfully and rejected if an error occurs.

Here’s an example that shows several promise instances with different states and values:

const promise1 = new Promise(function(resolve, reject) {
  // noop
});

console.log(promise1); // Promise {[[PromiseStatus]]: "pending", [[PromiseValue]]: undefined}


const promise2 = new Promise(function(resolve, reject) {
  resolve('foo');
});

console.log(promise2); // Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "foo"}


const promise3 = new Promise(function(resolve, reject) {
  reject(new Error('bar'));
});

console.log(promise3); // Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: Error: bar at <anonymous>:14:10 at Promise (<anonymous>) at <anonymous>:13:28}

If you run the above script in either a browser or Node.js, you should see console output similar to the comments. For now, ignore any errors related to uncaught or unhandled promise rejections (more on that later).

Note that this demo script is completely synchronous. Promises usually start out in a pending state and are resolved or rejected asynchronously. Once a promise has been resolved or rejected, its state and value become immutable.

then and catch 

To specify what should happen when the state of an instance changes, promises have two methods: then and catch. Both methods accept callback functions that may be invoked asynchronously at some point in the future.

The then method is typically used to specify what should happen when the promise is resolved, though it can accept a second function to handle rejections too. The catch method is explicitly used to handle rejections.

The callback functions passed to then and catch will receive the value passed through the when the resolve or reject functions are invoked in the resolver. The value passed through rejections should always be an instance of Error, though that’s not enforced.

Here’s an example that uses resolve and reject asynchronously. The then and catch methods are used to define what should happen when the promise’s state changes.

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);
  }); 
}

getRandomNumber()
  .then(function(value) {
    console.log('Async success!', value);
  })
  .catch(function(err) {
    console.log('Caught an error!', err);
  });

Here’s what’s going on in the script:

  • Lines 1-14: A function named getRandomNumber is defined. getRandomNumber returns a new promise instance. The resolver function uses setTimeout to simulate an async call which returns a random number. To demonstrate how error handling works, some random numbers will throw exceptions.

  • Line: 16: The getRandomNumber is invoked and immediately returns a promise in the pending state.

  • Lines 17-19: The then method of the promise is passed a callback that logs a success message with the resolved value.

  • Lines 20-22: The catch method of the promise is passed a callback that logs an error message with the rejected value (Error instance).

If you run the script above several times, you should see success messages and the occasional error message. Did you notice how the catch call flows off the then call? That technique is called promise chaining.

Promise Chaining

Calls to then and catch return new promise instances. Of course, these instances also have then and catch methods, which allows calls to be chained together as needed. Becuase these promises are not created with the constructor function, they are not resolved or rejected with a resolver function. Instead, if the function passed into then or catch completes without error, then the promise will be resolved. If the function returns a value, then it will set the promise’s value and be passed to the next then handler in the chain. If an error is thrown and goes unhandled, then the promise will be rejected and the error will be passed to the next error handler in the chain.

const myPromise = new Promise(function(resolve, reject) {
  resolve(42);
});

myPromise
  .then(function(value) {
    console.log('Got a value!', value);

    throw new Error('Error on the main thread');
  })
  .catch(function(err) {
    console.log('Caught the error without try/catch above');
    console.log(err);

    return 'foo';
  })
  .then(function(value) {
    console.log('Hey look, still going! We can simulate try...catch...finally!');
    console.log(value);
  });

If you run the above script with Node.js or in a browser, you should see output like the following (I used Node.js):

Got a value! 42
Caught the error without try/catch above
Error: Error on the main thread
    ...
Hey look, still going! We can simulate try...catch...finally!
foo

As you can see, errors thrown and values returned are routed to the next appropriate handler in the chain. This fact allows us to simulate a try…catch…finally block.

Things get more interesting when the value returned is a promise. When this happens, the next handler in the chain will not be invoked until that promise is resolved or rejected.

Here’s an example:

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);
  }); 
}

getRandomNumber()
  .then(function(value) {
    console.log('Value 1:', value);
    return getRandomNumber();
  })
  .then(function(value) {
    console.log('Value 2:', value);
    return getRandomNumber();
  })
  .then(function(value) {
    console.log('Value 3:', value);
  })
  .catch(function(err) {
    console.log('Caught an error!', err);
  });

With a little luck, when you run the script, you should see three random values printed to the console every two seconds. If an “error” occurs at any point in the chain, the remaining then functions will be skipped and the error will be passed to the next error handler in the chain. As you can see, promise chaining is a great way to run async operations in series without running into callback hell. But what about more complex flows?

For running operations in parallel, the Promise constructor function has all and race methods. These methods return promises that are resolved or rejected a little differently: all waits for all the promises passed in to be resolved or rejected while race only waits for the first.

Asynchronous iteration of collections, something that’s quite trivial with Async, is not so easy with promises. I’m not showing the technique here because there’s a much simpler way to do this now with async functions — just use a loop!

Error Handling

We’ve already covered some of the basics regarding error handling in the section on promise chaining. In this section, I want to explain something that often trips up folks that are new to promises.

Have a look at this example:

const myPromise = new Promise(function(resolve, reject) {
  resolve(42);
});

myPromise
  .then(function(value) {
    console.log('Got a value!', value);

    throw new Error('Error on the main thread');
  })
  .catch(function(err) {
    console.log('Caught the error without try/catch above');
    console.log(err);
  })
  .then(function() {
    console.log('Hey look, still going! We can simulate try...catch...finally!');

    throw new Error('Opps, bet you didn\'t see this error coming!');
  });

Running the script above should give you output like the following:

Got a value! 42
Caught the error without try/catch above
Error: Error on the main thread
    ...
Hey look, still going! We can simulate try...catch...finally!
(node:11780) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Opps, bet you didn't see this error coming!
(node:11780) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Note that unhandled errors thrown in functions passed to then and catch are swallowed up and treated like rejections. This means the error will be passed to the next error handler in the chain. But what happens if there are no more error handlers?

The demo script above throws two errors on the main thread. The first error is handled properly by the subsequent catch handler. However, there are no error handlers after the second error is thrown. This resulted in an unhandled rejection and the warnings in the console.

Typically in Node.js, when code throws errors on the main thread outside of a try/catch block, the process is killed. In the case of an unhandled rejection in a promise chain, Node.js emits an unhandledRejection event on the process object. If there’s no handler for that event, then you’ll get the UnhandledPromiseRejectionWarning seen above. According to the DeprecationWarning, unhandled promise rejections will kill the process in the future.

The solution is simple enough: be sure to handle those rejections! Check out this excellent post by Valeri Karpov to learn more about unhandled rejections.

Promise Demo App

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

package.json:

{
  "name": "promises",
  "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');

oracledb.createPool(dbConfig)
  .then(function() {
    return employees.getEmployee(101);
  })
  .then(function(emp) {
    console.log(emp);
  })
  .catch(function(err) {
    console.log(err);
  });

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 leverages the driver’s promise APIs to create a connection pool and start a promise chain. Although the pool is passed to the then handler for 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(function(resolve, reject) {
    let conn; // Declared here for scoping purposes.

    oracledb
      .getConnection()
      .then(function(c) {
        console.log('Connected to database');

        conn = c;

        return conn.execute(
          `select *
          from employees
          where employee_id = :emp_id`,
          [empId],
          {
            outFormat: oracledb.OBJECT
          }
        );
      })
      .then(
        function(result) {
          console.log('Query executed');

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

          reject(err);
        }
      )
      .then(function() {
        if (conn) {
          // If conn assignment worked, need to close.
          return conn.close();
        }
      })
      .then(function() {
        console.log('Connection closed');
      })
      .catch(function(err) {
        // If error during close, just log.
        console.log('Error closing connection', e);
      });
  });
}

module.exports.getEmployee = getEmployee;

In this version of the employees module, the getEmployee function was written as a promise-based API – it immediately returns a new promise instance which is asynchronously resolved or rejected. Internally, the function uses the driver’s promise based APIs to get a connection to the database, use it to execute a query, and then close a connection.

The promise chain is written in a way that simulates a try…catch…finally block. The final catch is there just in case there’s an error closing the connection (which would be rare). One could use the unhandledRejection  event as an alternative.

I hope this post has helped you to understand how promises work in Node.js. Remember that understanding promises is essential to understanding async functions, which I’ll cover in the next and final part of this series.

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!