Processing Results

The Neo4j JavaScript Driver provides you with two APIs for consuming results:

  • Promise API

  • Streaming API

The Promise API

The most common method of consuming results is with the Promise API.

When using session.run(), tx.run(), or one of the two transaction functions, the query will return a Promise.

Once the query has finished with all results results streamed to the Driver, the method resolves with a Result object.

Learn how to interact with Neo4j from Node.js using the Neo4j JavaScript DriverUsing async/await

Here is an example query call using async/await.

js
Async Example
try {
  const res = await session.executeRead(tx =>
    tx.run(
      'MATCH (p:Person) RETURN p.name AS name LIMIT 10'
    )
  )

  const names = res.records.map(row => {
    return row.get('name')
  })

  // `names` is an array of strings
  console.log(names)
}
catch (e) {
  // There was a problem with the
  // database connection or the query
  console.log(e)
}
finally {
  await session.close()
}

By using the await keyword, the Result object is assigned to the res variable once the database has stopped streaming records to the Driver.

Learn how to interact with Neo4j from Node.js using the Neo4j JavaScript DriverUsing Promises

Here is an example query utilizing the then() function from the returned Promise.

js
Using Promises
session.executeRead(tx => tx.run(
  'MATCH (p:Person) RETURN p.name AS name LIMIT 10')
)
  .then(res => {
    return res.records.map(row => {
      return row.get('name')
    })
  })
  .then(names => {
    // `names` is an array of strings
    console.log(names)
  })
  .catch(e => {
    // There was a problem with the
    // database connection or the query
    console.log(e)
  })
  // Finally, close the session
  .finally(() => session.close())

Once the database has stopped streaming records to the Driver, the Promise will resolve with a Result object.

The Result

The Result object, defined as res in the examples above, contains the total number of records received by the Driver along with a set of additional meta data about the query.

An individual row of results is referred to as a record, and can be accessed by the res.records array. A record refers to the keyed set of values specified in the RETURN portion of the statement. If no RETURN values are specified, the query will not return any results, and as a result res.records will be an empty array.

The meta data included in the Result include statistics on how many nodes and relationships were created, updated, or deleted as a result of the query.

Records

You can access the records returned by the query through the records property. This property is a native JavaScript array which can be iterated over, for example using the .map() or .foreach() methods.

js
Iterating over the Records array
const names = res.records.map(row => {
  return row.get('name')
})

Key or Index

You can either access a value within the record by using the alias as specified in the RETURN portion of the Cypher statement or by specifing the column number as an integer. The available keys can be accessed through res.keys().

You can also iterate over a result within a for loop.

js
Using AsyncIterator
for (const record in res.records) {
  console.log(record.get('name'))
}

Result Summary

You can access summary information about the query through the summary property. Included in this summary is information about the server, the query, execution times, and a counters object which provide statistics about the query.

For example, to get information about how long the query took to complete, you can use the following property:

js
Using the Result Summary
// Time in milliseconds before receiving the first result
console.log(res.summary.resultAvailableAfter) // 10

// Time in milliseconds once the final result was consumed
console.log(res.summary.resultConsumedAfter) // 30

Another interesting part of the summary is the counters property, which holds numerical data about the queries execution. To access statistics as a result of a Write Transaction, you can call the updates() method on the counters property:

js
Result Counters
console.log(res.summary.counters.updates())

/*
{
  nodesCreated: 10,
  nodesDeleted: 0,
  relationshipsCreated: 5,
  relationshipsDeleted: 0,
  propertiesSet: 20,
  labelsAdded: 10,
  labelsRemoved: 0,
  indexesAdded: 0,
  indexesRemoved: 0,
  constraintsAdded: 0,
  constraintsRemoved: 0
}
*/

The Streaming API

The drawback to the Promise API method of consuming results, is that the Promise will only resolve after all results have been received by the Driver.

There may be occasions where you have more complex, or slower-running queries in your application but wish to update a front-end application as results become available. For example, many Flight search websites have complex queries that take up to a minute to complete, but simple, more expensive flights may be available immediately.

In this case, you can use the subscribe() method to consume results as they become available. This method can be used when calling session.run() or tx.run() within a transaction function.

You can use this method with websockets, or a similar technology to update the front-end application with new results as they are made available.

The subscribe() method accepts one argument, an object of callbacks:

  • onKeys - is called when keys are made available for all records.

  • onNext - called when the next record is available.

  • onError - called if an error is thrown.

  • onCompleted - called when all records have been consumed and no error has been thrown.

js
The Streaming API
// Run a Cypher statement, reading the result in a streaming manner as records arrive:
session
  .run('MERGE (alice:Person {name : $nameParam}) RETURN alice.name AS name', {
    nameParam: 'Alice'
  })
  .subscribe({
    onKeys: keys => {
      console.log(keys) // ['name]
    },
    onNext: record => {
      console.log(record.get('name')) // 'Alice'
    },
    onCompleted: (summary) => {
      // `summary` holds the same information as `res.summary`

      // Close the Session
      session.close()
    },
    onError: error => {
      console.log(error)
    }
  })

Reactive Streams with RxJS

The Drivers also offer out-of-the-box support for RxJS streams. To create a reactive session, call the rxSession() method on the driver.

The execution of the query is almost identical, with the exception of a records() method which returns an RxJS Observable, to which you can apply operators.

js
Reactive Streams
const res = await rxSession
  .executeWrite(txc =>
    txc
      .run('MERGE (p:Person) RETURN p.name AS name LIMIT 10')
      .records()
      .pipe(
        map(record => record.get('name'))
      )
  )

Check Your Understanding

1. What is the drawback of using the Promise API to consume results?

  • ❏ The Promise API is only available to Enterprise customers.

  • ✓ Results are only available once the Driver has received the final record.

  • ❏ You can only use the Promise API within a Read Transactions.

Hint

If you are not subscribing to the record stream, you will only be able to access the first record once the entire stream has finished.

Solution

Results are only available once the Driver has received the final record. This can provide a negative experience for users waiting for the results of a long-running query.

Lesson Summary

You now have all of the information required to send Cypher queries to Neo4j and consume the results.

Next, we will look at the Cypher Type System and some of the considerations that you need to make when working with values coming from Neo4j in your Node.js application.