J@ArangoDB

{ "subject" : "ArangoDB", "tags": [ "multi-model", "nosql", "database" ] }

More ES6 Features

ArangoDB 2.5 comes with an upgraded version of V8, Google’s open source JavaScript engine.

The built-in version of V8 has been upgraded from 3.29.54 to 3.31.74.1.

In addition to several already usable ES6 features (detailed in this blog, the following ES6 features are activated in ArangoDB 2.5 by default:

  • iterators and generators
  • template strings
  • enhanced object literals
  • enhanced numeric literals
  • block scoping with let and constant variables using const
  • additional String methods (such as startsWith, repeat etc.)

The above features are available in ArangoDB 2.5, and can now be used for scripting purposes in the ArangoShell and in server-side Foxx actions inside the database.

This blog post briefly explains the features provides some quick examples for using them.

Iterators and generators

Iterator and generator support was optional in 2.4, but is turned on by default since 2.5.

For everyone who is not familiar with generators in JavaScript, here’s how they work:

Generators are special functions tagged with an asterisk (*). Values are returned to the caller using the yield keyword:

a simple generator that generates two values
1
2
3
4
function* generate () {
  yield 23;
  yield 42;
}

Calling the function with initialize/reset the generator. Calling the next() method on the generator’s initial call return value produces the next sequence element. The element is returned in a value attribute. The done attribute indicates whether the sequence has come to an end:

invoking the generator
1
2
3
4
var generator = generate();
console.log(generator.next());  /* { "value" : 23, "done" : false } */
console.log(generator.next());  /* { "value" : 42, "done" : false } */
console.log(generator.next());  /* { "value" : undefined, "done" : true } */

Sequences produced by generators can also be consumed via a for...of loop:

consuming all values from a generator function
1
2
3
4
5
var generator = generate();

for (var value of generator) {
  console.log(value);
}

In general, every object that is iteratable can be consumed using the of operator. Some built-in objects provide pre-defined iterators (e.g. Map.keys() or Map.values()), but you can also create iterators for your own objects:

creating an iterator for an object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Sentence (text) {
  this.text = text;
}

Sentence.prototype[Symbol.iterator] = function*() {
  var regex = /\S+/g;
  var text = this.text;
  var match;
  while (match = regex.exec(text)) {
    yield match[0];
  }
};

var sentence = new Sentence("The quick brown fox jumped over the lazy dog");
for (var word of sentence) {
  console.log(word);
}

Template strings

I know there are query string generators and such, but for the sake of the example, let’s assume you wanted to write a query string in JavaScript. You might end up with something like this:

multi-line query string
1
2
3
4
var query =
  'FOR doc IN users\n' +
  '  FILTER doc.name == @name\n' +
  '  RETURN doc\n';

This is hardly legible, and it is also very prone to errors.

ES6 template strings provide a way to define multi-line string literals in a much easier and simpler way. Here’s how to do it (note the backticks instead of the regular string quotes):

using a multi-line template string
1
2
3
4
5
var query = `
FOR doc IN users
  FILTER doc.name == @name
  RETURN doc
`;

Template strings also support value substitution, so you could even write something like this, too:

value substitution in template strings
1
2
3
4
5
6
7
var name = "AQL injection attempt \" OR true OR \"";

var query = `
FOR doc IN users
  FILTER doc.name == ${JSON.stringify(name)}
  RETURN doc
`;

Note that while value substitution in template strings in convenient, you still have to be careful with user-generated values. Otherwise you might be subject to value injection attacks, as you would be with every other form of improper user value handling.

Enhanced object literals

Save some time when definining objects:

using enhanced object literals
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = "foobar";

myObject = {
  type : "myType",   /* always worked */
  name,              /* same as "name" : name */
  save () {          /* same as "save" : function () ... */
    console.log("save called!");
  }
};

{
  "type" : "myType",
  "name" : "foobar",
  "save" : [Function "console.log("save called!");" ...]
}

As can be seen above, enhanced object literal declarations can save some typing and reduce redundancies in the code. Unfortunately we still cannot use object key names generated from expressions:

does not work yet
1
2
3
myObject = {
  [ "foo" + bar" ] : "foobar"
};

Enhanced numeric literals

Numeric values can now be specified in binary or octal if required:

numeric literals
1
2
var life = 0b101010;          /* binary, 42 in decimal */
var filePermissions = 0o777;  /* octal, 511 in decimal */

Block scoping

As a mostly C++ programmer, I am always puzzled about the scoping of JavaScript variables. In the following example, variable x does not only live inside the curly brackets block in which it was declared, but also afterwards:

numeric literals
1
2
3
4
5
6
function work () {
  {
    var x = 1;
  }
  return x;
}

The reason is that the curly brackets around var x = 1; are not a scope at all in traditional JavaScript. This sucks, because variables can linger around in programs longer than necessary, leading to unwanted side-effects.

With block-level scopes, this can be fixed. To use it, introduce variables not with the var keyword, but with let. let only works in strict mode, so make sure your function or module uses it.

Now, with block-level scoping, the above snippet looks like this:

numeric literals
1
2
3
4
5
6
7
function work () {
  "use strict";
  {
    let x = 1;
  }
  return x;
};

And it will actually produce an error when trying to access variable x in the return statement. The reason is that the life of x was over as soon as its scope was left. The scope of variable x is now only the one with the let declaration inside the curly brackets.

Someone else said “let is the new var”, and I think there’s not much to add.

Additionally, the const keyword can be used to define a read-only variables. Trying to re-define a constant will produce an error in strict mode (the desired behavior), and do nothing in non-strict mode. Another reason to use the strict mode.

Additional String methods

String objects now provide extra built-in methods:

  • string.startsWith(what)
  • string.endsWith(what)
  • string.includes(what)
  • string.repeat(count)
  • string.normalize(method)
  • string.codePointAt(position)

There is also an extra “static” method:

  • String.fromCodePoint(codePoint)