No Semicolons.
On the client-side, just use Babel for cross-browser compatibility.
Server-side debugging with source maps is tricky according to some and fine according to others.
app.get('/', function (req, res) {
Widgets.createWidgets(20, function (widgets) {
Gadgets.createGadgetsFromWidgets(widgets, function (gadgets) {
Things.createThingsFromGadgets(gadgets, function (things) {
res.set('Content-Type', 'application/json')
res.send(JSON.stringify(things))
})
})
})
})
Not the messiest example
app.get('/', function (req, res) {
Widgets.createWidgets(20, widgetsCallback)
function widgetsCallback (widgets) {
Gadgets.createGadgetsFromWidgets(widgets, gadgetsCallback)
}
function gadgetsCallback (gadgets) {
Things.createThingsFromGadgets(gadgets, thingsCallback)
}
//res is available from the Express.js server endpoint
function thingsCallback (things) {
res.set('Content-Type', 'application/json')
res.send(JSON.stringify(things))
}
})
Flatter but harder to follow (arguably)
app.get('/', function (req, res) {
Widgets.createWidgets(20)
.then(function (widgets) {
return Gadgets.createGadgetsFromWidgets(widgets)
}).then(function (gadgets) {
return Things.createThingsFromGadgets(gadgets)
}).then(function (things) {
res.set('Content-Type', 'application/json')
res.send(JSON.stringify(things))
}).catch(function (error) {
//handle errors
})
})
Much better
app.get('/', function (req, res) {
async(function * (next) {
try {
let widgets = yield Widgets.createWidgets(20)
let gadgets = yield Gadgets.createGadgetsFromWidgets(widgets)
let things = yield Things.createThingsFromGadgets(gadgets)
res.set('Content-Type', 'application/json')
res.send(JSON.stringify(things))
} catch (e) {
//handle errors
}
})
})
This lets us yield each promise in a synchronous fashion without blocking the thread from executing other code.
app.get('/', function (req, res) {
async(function * (next) {
try {
let widgets = yield Widgets.createWidgets(20)
let gadgets = yield Gadgets.createGadgetsFromWidgets(widgets)
let things = yield Things.createThingsFromGadgets(gadgets)
res.set('Content-Type', 'application/json')
res.send(JSON.stringify(things))
} catch (e) {
//handle errors
}
})
})
function * evenNumbers () {
for(var i = 1; true; i++) {
if(i % 2 === 0) {
yield i
}
}
}
> x = evenNumbers()
evenNumbers {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
> x.next()
Object {value: 2, done: false}
> x.next()
Object {value: 4, done: false}
> x.next()
Object {value: 6, done: false}
> x.next()
Object {value: 8, done: false}
> x.next()
Object {value: 10, done: false}
Definition:
function * evenNumbersStoppable () {
for(var i = 1; true; i++) {
if(i % 2 === 0) {
var stop = yield i
if(stop === true) {
return i
}
}
}
}
Output:
> y = evenNumbersStoppable()
evenNumbersStoppable {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
> y.next()
Object {value: 2, done: false}
> y.next()
Object {value: 4, done: false}
> y.next()
Object {value: 6, done: false}
> y.next()
Object {value: 8, done: false}
> y.next(true)
Object {value: 8, done: true}
> y.next()
Object {value: undefined, done: true}
> y.next()
Object {value: undefined, done: true}
To stop a generator, return a value instead of yielding it.
> x = evenNumbersStoppable()
evenNumbersStoppable {[[GeneratorStatus]]: "suspended", [[GeneratorReceiver]]: Window}
> x.next()
Object {value: 2, done: false}
> x.next()
Object {value: 4, done: false}
> x.throw(new Error('Something has gone terribly wrong here'))
Uncaught Error: Something has gone terribly wrong here(…)(anonymous function) @ VM6460:2InjectedScript._evaluateOn @ (program):878InjectedScript._evaluateAndWrap @ (program):811InjectedScript.evaluate @ (program):667
> x.next()
Object {value: undefined, done: true}
Generators have a built in function for throwing errors and stopping itself.
// copied (and lightly modified) from: https://www.promisejs.org/generators/
function async (makeGenerator){
return function () {
//start the generator function
var generator = makeGenerator.apply(this, arguments)
try {
return handle(generator.next())
} catch (ex) {
return Promise.reject(ex)
}
function handle (result){
// result => { done: [Boolean], value: [Object] }
if (result.done) return Promise.resolve(result.value)
return Promise.resolve(result.value).then(function (res){
return handle(generator.next(res))
}, function (err){
return handle(generator.throw(err))
});
}
}
}
This wrapper allows us to yield the result of a promise or handle the error of it with a try/catch.
A Sequence of Operations:
var get = async(function * (){
var left = yield readJSON('left.json')
var right = yield readJSON('right.json')
return {left: left, right: right}
})
Parallel Operations:
var get = async(function * (){
var left = readJSON('left.json')
var right = readJSON('right.json')
return {left: yield left, right: yield right}
})
A Sequence of Operations:
var get = async function (){
var left = await readJSON('left.json')
var right = await readJSON('right.json')
return {left: left, right: right}
}
Parallel Operations:
var get = async function (){
var left = readJSON('left.json')
var right = readJSON('right.json')
return {left: await left, right: await right}
}
Looking similar? There is a reason.
async function <name>?<argumentlist><body>
function <name>?<argumentlist>{ return spawn(function*() <body>, this); }
async desugars into a promise handling wrapped generator.
function spawn(genF, self) {
return new Promise(function(resolve, reject) {
var gen = genF.call(self);
function step(nextF) {
var next;
try {
next = nextF();
} catch(e) {
// finished with failure, reject the promise
reject(e);
return;
}
if(next.done) {
// finished with success, resolve the promise
resolve(next.value);
return;
}
// not finished, chain off the yielded promise and `step` again
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
Convert a normal middleware function to a generator.
Looking much cleaner.
So Simple. This part of the process could probably even be automated.
It is low level like Express.
Much more info about Koa on the wiki on Github: https://github.com/koajs/koa/wiki