On Github ryanramage / living-the-stream-dream
By Ryan Ramage / @ryan_ramage_
I am ryan ramage, some may know me as ryan_ramage_ on twitter I work at Redman Technologies (formerly with Sean, and with fellow presenter tonight Nick) I am a CouchDB committer Full stack javascript'r I have a firefox phoneI will talk tonight on 'Living the steam dream'. A high level preso on streams, why they are good, and some helpful tools
Get the shituff to the user as fast as possible
Because Speed is King in applications
Garden hose
Dealing with disk/network/io/processor variability
Keep memory spikes down
The internet was built for streams.
-Wikipedia on TCP Flow control
Wrapping xhr with a progressive streaming interface A JSON parser that sits somewhere between SAX and DOM.
Dealing with failure mid request/response
Sarah is sitting on a train using her mobile phone to check her email. The phone has almost finished downloading her inbox when her train goes under a tunnel. Luckily, her webmail developers used Oboe.js so instead of the request failing she can still read most of her emails. When the connection comes back again later the webapp is smart enough to just re-request the part that failed.Incremental loading, and browser caching
Arnold has a query that is many-dimensional which sometimes takes a long time. To speed things up, each result can be streamed and displayed as soon as it is found. Since Oboe isn't true streaming it plays nice with the browser cache. Later, Arnold revisits the same query page. he see the same results instantly from cache.Easy JSON slice and dice
Oboe.js provides a neat way to route different parts of a json response to different parts of his application. One less bit to write.GET /myapp/things
{ "foods": [ {"name":"aubergine", "colour":"purple"}, {"name":"apple", "colour":"red"}, {"name":"nuts", "colour":"brown"} ], "badThings": [ {"name":"poison", "colour":"pink"}, {"name":"broken_glass", "colour":"green"} ] }
MyApp.showSpinner('#foods'); oboe('/myapp/things') .node('foods.*', function( foodThing ){ $.templateThing.append( foodThing.name + ' is ' + foodThing.colour ); }) .node('badThings.*', function( badThing ){ console.log( 'Danger! stay away from ' + badThings.name ); }) .done( function(things){ MyApp.hideSpinner('#foods'); console.log( 'there are ' + things.foods.length + ' things you can eat ' + 'and ' + things.nonFoods.length + ' that you shouldn\'t.' ); });
oboe('/myapp/things') .node('{name colour}', function( foodObject ) { // I'll get called for every object found that // has both a name and a colour };
Streaming between node and the browser
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { fs.readFile(__dirname + '/movie.mp4', function (err, data) { res.end(data); }); }); server.listen(8000);
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { var stream = fs.createReadStream(__dirname + '/movie.mp4'); stream.pipe(res); }); server.listen(8000);
var fs = require("fs"); // Read File fs.createReadStream("input/people.json") // Write File .pipe(fs.createWriteStream("output/people.json"));
All the different types of streams use .pipe() to pair inputs with outputs.
.pipe() is just a function that takes a readable source stream src and hooks the output to a destination writable stream dst:
src.pipe(dst)
a.pipe(b); b.pipe(c); c.pipe(d);
.pipe(dst) returns dst so that you can chain together multiple .pipe() calls together, which is the same as:
a.pipe(b).pipe(c).pipe(d)
This is very much like what you might do on the command-line to pipe programs together:
a | b | c | d
var fs = require("fs"); var zlib = require("zlib"); // Read File fs.createReadStream("input/people.csv.gz") // Un-Gzip .pipe(zlib.createGunzip()) // Write File .pipe(fs.createWriteStream("output/people.csv"));
Advice: Watch for > 1 year inactivity. Streams have changed a lot
Look for trusted names in the stream eco-system
You can stream any response to a file stream.
request('http://google.com/doodle.png').pipe(fs.createWriteStream('doodle.png'))
The EventStream functions resemble the array functions, because Streams are like Arrays, but laid out in time, rather than in memory.
Example of event stream
var inspect = require('util').inspect var es = require('event-stream') //load event-stream es.pipe( //pipe joins streams together process.openStdin(), //open stdin es.split(), //split stream to break on newlines es.map(function (data, callback) {//turn this async function into a stream var j try { j = JSON.parse(data) //try to parse input into json } catch (err) { return callback(null, data) //if it fails just pass it anyway } callback(null, inspect(j)) //render it nicely }), process.stdout // pipe it to stdout ! ) // curl -sS registry.npmjs.org/event-stream | node pretty.js //
> npm install -g stream-adventure > stream-adventure
request('http://localhost:5984/db/_design/app/_view/things_by_date').pipe(resp);
var filter = function(data, emit) { data.forEach(function(db){ if (db.indexOf('dumb_user-') === 0) { emit('"' + strip_prefix(db) + '"'); } }); } var filter_through = new FilterThrough(filter); request('http://localhost:5984/_all_dbs') .pipe(filter_through) .pipe(resp);
00:00: > curl -X GET "http://localhost:5984/db/_changes?feed=continuous&since=3&filter=app/important" {"seq":4,"id":"test4","changes":[{"rev":"1-02c6b758b08360abefc383d74ed5973d"}]} {"seq":5,"id":"test5","changes":[{"rev":"1-02c6b758b08360abefc383d74ed5973d"}]}Note that the continuous changes API result doesn’t include a wrapping JSON object it includes only a raw line per notification. Also note that the lines are no longer separated by a comma. Note the since, and filter. Ways to slice what you want to watch for
var levelup = require('levelup'); var srcdb = levelup('./srcdb'); var dstdb = levelup('./destdb'); srcdb.put('name', 'LevelUP'); srcdb.createReadStream().pipe(dstdb.createWriteStream()).on('close', onDone)
srcdb.createReadStream({ start: 'n', end: 'k' }).pipe(resp)
var scriptFiles = './src/**/*.js'; gulp.task('compile', function(){ // concat all scripts, minify, and output gulp.src(scriptFiles) .pipe(concat({fileName: pkg.name+".js"}) .pipe(minify()) .pipe(gulp.dest('./dist/')); }); gulp.task('test', function(){ // lint our scripts gulp.src(scriptFiles).pipe(jshint()); // run our tests spawn('npm', ['test'], {stdio: 'inherit'}); }); gulp.task('default', function(){ gulp.run('test', 'compile'); gulp.watch(scriptFiles, function(){ gulp.run('test', 'compile'); }); });
// please provide class People extends MVCrap { getAll() getByX() }
stream-to-pull-stream: Convert a classic-stream, or a new-stream into a pull-stream
invert-stream: Create a pair of streams (A, B) such that A.write(X) -> B.emit('data', X) and B.write(X) -> A.emit('data', X)
Mix and match:
Try them out today!
By Ryan Ramage / @ryan_ramage_