ECMAScript 6 (ES6) is getting closer and closer with more support available in both node 0.12.0 and io.js. ES6 promises to fundamentally change the way we develop applications on node.js by allowing for the use of generators for iterators and a standard promises library for orchestrating our asynchronous calls. In this talk we will explore the implications of ES6 for the node driver and applications written on top of the driver.
4. 4
Good Evening
Christian Amor Kvalheim
Drivers Lead and Node.js driver author
Oviedo, Spain
@christkv
christkv@mongodb.com
http://www.christiankvalheim.com
6. 6
ES6
• ES6 Is a fundamental rethink of the JavaScript language
• Introduces a lot of new features
–Iterators
–Generators
–Proxies
–Promises
–Classes
–and much more….
8. 8
Classes
• Classes are syntactic sugar on top of JavaScript existing
prototype based inheritance.
• Classes do not introduce a new Object Oriented
inheritance model.
• Classes simplify the code and provides a clearer syntax
for prototype based inheritance.
11. 11
Iterators
• Iterators are created using a protocol allowing JavaScript
objects to define or customize their iteration behavior.
• Arrays in ES6 are now iterators and can be used in the
new for-of construct.
• An object is an Iterator when it implements a next()
method that returns an object with two properties.
–done (boolean)
–value (any JavaScript value)
12. 12
Iterators
class Next {
constructor(value) { this.value = value || 0; }
}
Next.prototype[Symbol.iterator] = function() {
var cur = this.value;
return {
next() { return {done: false, value: cur++} }
}
}
for(var n of new Next()) {
if(n == 1000) break;
}
13. 13
Promises
• A Promise is an object used for deferred and
asynchronous computations.
• A promise can either be fulfilled with a value or rejected.
• Promises can be chained using then.
• Execute n promises using Promise.all.
• Very useful to orchestrate asynchronous operations.
• Allows for easier reasoning of call chains.
14. 14
Promises
var p2 = new Promise(function(resolve, reject) {
resolve(2);
});
p2.then(function(value) {
assert(2, value);
});
var promise = Promise.resolve(3);
Promise.all([true, promise])
.then(function(values) {
console.log(values);
});
15. 15
Generators
• A Generator is a function that can be exited, and then
later re-entered.
• The context of the function is saved across re-entrances.
• Calling a Generator function returns an iterator object.
• Calling next on the iterator executes the function until the
first yield is encountered.
16. 16
Generators
class Next {
constructor(value) { this.value = value || 0; }
}
Next.prototype[Symbol.iterator] = function*() {
var cur = this.value;
while(true) {
yield cur++;
}
}
for(var n of new Next()) {
if(n == 1000) break;
}
17. 17
Generators + co
• co library
–Runs a generator to the end
–Expects to yield on promises
–Simplifies the syntax for programming
18. 18
Generators + co
var co = require('co'),
assert = require('assert');
var p1 = new Promise(function(resolve, reject) {
resolve(2);
});
co(function*() {
var value = yield p1;
assert.equal(2, value);
}).catch(function(err) {
console.dir(err)
});
19. 19
Proxies
• Proxies allows a user to override fundamental behaviors
of an object
–Property lookup
–Assignment
–Function calls
–Enumeration
• Proxies is close to method_missing in ruby.
• Proxies can lead to slow execution if in critical code path.
20. 20
Proxies
var handler = {
get: function(target, name){
return name in target ? target[name] : 37;
}
};
var p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log('c' in p, p.c); // false, 37
22. 22
MongoDB Core
• MongoDB Core is a low level driver wrapping the wire
protocol and authentication
–No abstractions
• MongoDB Core was developed to allow for people to
more easily write
–ODMs (Object Document Modeling)
–Driver Wrappers
–Tools/Frameworks
23. 23
Simple Example
var Server = require('mongodb-core').Server;
// Attempt to connect
var server = new Server({ host: 'localhost', port: 27017 })
// Add event listeners
server.on('connect', function(server) {
// Execute the command
server.command("system.$cmd"
, {ismaster: true}, function(err, result) {
server.destroy();
});
});
server.connect();
26. 26
ES6 Prototype
• Prototype aims to explore the ES6 space to improve the
usability of the driver.
–Leverage only the features of ES6 that improves
productivity
–Ensure no significant performance degradation
happens in the transition from ES5 to ES6
27. 27
FROM
var MongoClient = require(‘mongodb’).MongoClient,
assert = require(‘assert’);
MongoClient.connect(‘mongodb://localhost:27017/test’
, function(e, d) {
assert.equal(null, e);
var c = d.collection(‘test1’);
c.insertOne({a:1}, function(e, r) {
assert.equal(null, e);
c.find({}).toArray(function(e, docs) {
assert.equal(null, e);
assert.equal(1, docs.length);
d.close();
});
});
});
28. 28
TO
var MongoClient = require(’mongodb-es6').MongoClient,
co = require(‘co’);
co(function* () {
var client = yield new
MongoClient('mongodb://localhost:27017/test', {}).connect()
try {
var result = yield client['tests']['cursors'].insertOne({a:2});
} catch(err) {};
var docs = yield client['tests']['cursors'].find({}).toArray();
}).catch(function(err) {
console.dir(err)
});
29. 29
ES6 Driver Design
• Driver uses MongoDB Core as it’s bases.
• Driver leverages the following features of ES6
– Classes
– Promises
• Forward compatible with co
• Forward compatible with ES7 async/await
– Proxies
30. 30
Classes
• Prototype has classes for
– Db
– Collection
– Cursor
– AggregationCursor
– CommandCursor
– MongoClient
• Prototype has a minimal API
31. 31
Promises
• Prototype returns a Promise for all asynchronous
methods, no callbacks.
command(cmd) {
var self = this;
return new this.options.promise(function(resolve, reject) {
self.topology.command(f('%s.$cmd', self.name), cmd,
function(err, r) {
if(err) reject(err);
else resolve(r);
});
});
}
32. 32
Promises
• Prototype returns standard ES6 promises
• Bring Your Own Promises (BYOP)
var MongoClient = require(’mongodb-es6').MongoClient
, P = require("bluebird");
co(function* () {
var client = yield new MongoClient('mongodb://localhost:27017/test’)
.promiseLibrary(P).connect();
});
33. 33
Promises + Iteration
• Iterating a cursor is completely different from 2.x
var MongoClient = require(’mongodb-es6').MongoClient
, P = require("bluebird");
co(function* () {
var client = yield new MongoClient('mongodb://localhost:27017/test’)
.promiseLibrary(P).connect();
var cursor = client[‘tests’][‘docs’].find({});
while(yield cursor.hasNext()) {
console.dir(yield cursor.next());
}
}).catch(function(err) {
console.dir(err);
});
34. 34
Proxies
• Prototype uses proxies to allow for simpler access to
databases allowing you to do.
var MongoClient = require(’mongodb-es6').MongoClient,
co = require(‘co’);
co(function* () {
var client = yield new
MongoClient('mongodb://localhost:27017/test', {}).connect();
var testsDb = yield client['tests’];
var cursorsCollection = yield testsDb['cursors’]
}).catch(function(err) {
console.dir(err)
});
35. 35
Proxies Gotchas
• Proxies come with some performance implications.
• Proxies are inherently poison to the way V8 JITs into
stable classes as a proxy wrapped instance can be x
possible classes forcing de-optimizations.
for(var i = 0; i < 10; i++) {
yield client[‘tests’][‘docs’].insertOne({a:1});
}
var col = client[‘tests’][‘docs’];
for(var i = 0; i < 10; i++) {
yield col.insertOne({a:1})
}
37. 37
Performance
2.0 (IO 2.0) simple_100_document_toArray
Average 0.414 ms
Standard Deviation 0.498 ms
ES6 (IO 2.0) simple_100_document_toArray
Average 0.415 ms
Standard Deviation 0.501 ms