Генераторы в node

34
Генераторы в Node.js Михаил Качановский , GlobalLogic

Upload: michael-kachanovskyi

Post on 13-Apr-2017

192 views

Category:

Technology


0 download

TRANSCRIPT

Генераторы в Node.js

Михаил Качановский, GlobalLogic

$ nvm install v0.11.2

$ node -v 0.11.2-9

$ node --harmony generators.js

Основы function* two() { yield 1; yield 2; }

var gen = two(); console.log( gen.next() ); // { value: 1, done: false } console.log( gen.next() ); // { value: 2, done: false } console.log( gen.next() ); // { value: undefined, done: true }

console.log( gen.next() ); // Error: Generator has already finished

Итератор

function* odd(limit) { for (var i = 0; i < limit; i++) { if (i % 2) yield i; } }

for (var i of odd(10)) { console.log(i); } // 1 3 5 7 9

Бесконечные последовательности

function fibonacci(){ var fn1 = 1; var fn2 = 1; while (1){ var current = fn2; fn2 = fn1; fn1 = fn1 + current; yield current; } }

var sequence = fibonacci(); console.log(sequence.next()); // 1 console.log(sequence.next()); // 1 console.log(sequence.next()); // 2 console.log(sequence.next()); // 3 console.log(sequence.next()); // 5

Передача параметров

function* fibonacci(){ var fn1 = 1; var fn2 = 1; while (1){ var current = fn2; fn2 = fn1; fn1 = fn1 + current; var reset = yield current; if (reset){ fn1 = 1; fn2 = 1; } } }

var sequence = fibonacci(); console.log(sequence.next()); // 1 console.log(sequence.next()); // 1 console.log(sequence.next()); // 2 console.log(sequence.next()); // 3 console.log(sequence.next(true)); // 1 console.log(sequence.next()); // 1 console.log(sequence.next()); // 2

Рекурсия

function* factorial(n) { return n === 0 ? 1 : n*(yield* factorial(n-1)); }

var gen = factorial(5); console.log(gen.next()); // { value: 120, done: true }

CALLBACK HELL

fs.readdir(source, function(err, files) { if (err) { console.log('Error finding files: ' + err) } else { files.forEach(function(filename, fileIndex) { gm(source + filename).size(function(err, values) { if (!err) { widths.forEach(function(width, widthIndex) { // ... }.bind(this)) } }) }) } }

Чтение JSON

function readJSONSync(filename) { return JSON.parse(fs.readFileSync(filename, 'utf8')) }

Асинхронный вариант

function readJSON(filename, callback) { fs.readFile(filename, 'utf8', function (err, res){ if (err) return callback(err) callback(null, JSON.parse(res)) }) }

Асинхронный вариант 2

function readJSON(filename, callback) { fs.readFile(filename, 'utf8', function (err, res){ if (err) return callback(err) try { callback(null, JSON.parse(res)) } catch(ex) { callback(ex); } }) }

Асинхронный вариант 3

function readJSON(filename, callback) { fs.readFile(filename, 'utf8', function (err, res){ if (err) return callback(err) try { res = JSON.parse(res) } catch (ex) { return callback(ex) } callback(null, res) }) }

Promise

var Q = require('q');

function readFile(filename) { var deffered = Q.defer(); fs.readFile(filename, function(err, res) { err ? deffered.reject(err) : deffered.resolve(res); }); return deffered.promise; }

Асинхронный вариант с promise

function readJSON(filename){ return readFile(filename).then(function (res){ return JSON.parse(res) }); }

function readJSON(filename){ return readFile(filename).then(JSON.parse}); }

Что не так с promise?

readJSON(file1).then(function(content1) { // do something return readJSON(file2); }).then(function(content2) { // ... }).then(function() { // ... }).then(function() { // ... });

Последовательность операций

db.connect(options).then(function() { return db.getPostById(postId); }).then(function(post) { var tags = post.tags.split(',');

return Q.all(tags.map(function(tag) { return db.getPostsByTag(tag); })).then(function(taggedPosts) { db.disconnect(); }); });

ГенераторыПочему бы не использовать их для того, чтобы подождать

promise?

Чтение JSON синхронно

function readJSONSync(filename) { return JSON.parse(fs.readFileSync(filename, 'utf8')) }

Чтение JSON асинхронно

var readJSON = Y(function* (filename) { return JSON.parse(yield readFile(filename, 'utf8')); });

Последовательность операций

Y(function* () { yield db.connect(options);

var post = yield db.getPostById(postId);

var tags = post.tags.split(',');

var taggedPosts = tags.map(function(tag) { return yield db.getPostsByTag(tag); });

db.disconnect(); })();

Обработка ошибок

Y(function* () {

try { var content1 = yield read(file1); var content2 = yield read(file2); var content3 = yield read(file3); } catch(e) { console.log("Error: " + e.message); }

})();

Как это работает?

function* read() { var content = yield readFile('basic.js'); console.log(content.length); }

var gen = read();

var promise = gen.next().value;

promise.then(gen.next.bind(gen), gen.throw.bind(gen));

function Y(fn) { return function() { var generator = fn.apply(this, arguments);

function handle(result) { return result.done ? result.value : result.value.then(function (res){ return handle(generator.next(res)) }, function (err) { return handle(generator.throw(err)) }); }

return hande(generator.next()); } }

SUSPEND

var suspend = require('suspend');

suspend(function* (resume) { return JSON.parse(yield fs.readFile(__filename, 'utf8', resume)); });

CO

function read(file) { return function(fn){ fs.readFile(file, 'utf8', fn); } }

var co = require('co');

co(function *(){ var a = yield read('.gitignore'); var b = yield read('Makefile'); var c = yield read('package.json'); console.log(a.length); console.log(b.length); console.log(c.length); })();

Q.ASYNC

var generator = Q.async(function* () { var ten = yield 10; console.log(ten, 10); var twenty = yield ten + 10; console.log(twenty, 20); var thirty = yield twenty + 10; console.log(thirty, 30); return thirty + 10; });

generator().then(function (forty) { console.log(forty, 40); }, function (reason) { console.log("reason", reason); });

YIELDING

var Y = require('yielding');

var c = Y(function* () { var a = yield 1; var b = yield 2; return a + b; });

console.log( c.once() ); // 1 console.log( c() ); // 3

toArray()

var odd = Y(function* (limit) { for (var i = 0; i < limit; i++) { if (i % 2) yield i; } });

console.log( odd.toArray(10) ); // 1,3,5,7,9

Последовательное выполнение

var get = Y.nwrap( require('request').get );

Y(function* () { var pages = ['http://google.com', 'http://yahoo.com', 'http://bing.com']; var content = pages.map(function(url) { return yield get(url); }); console.log(content.map(function(c) { return c.body.length; })); })();

Параллельное выполнение

var get = Y.nwrap( require('request').get );

Y(function* () { var pages = ['http://google.com', 'http://yahoo.com', 'http://bing.com']; var content = yield pages.map(function(url) { return get(url); }); console.log(content.map(function(c) { return c.body.length; })); })();

SUSPEND-style

Y(function *async() { var result = yield void setTimeout(function () { async.resume(null, 123); }, 200); expect(result).to.be.equal(123); done(); })();

Y(function *async() { var content = yield fs.readFile('test/example.txt', 'utf8', async.resume); })();

var resume = arguments.callee.resume;

Производительность

co(function *(){ var d = +new Date(); for (var i = 1; i < 50000; i++) { var a = yield read('benchmark/1.txt', 'utf8'); } console.log('Reading 50000 times: ' + (+new Date() - d) + 'ms'); })();

CO: ~4.6syielding: ~4.75ssuspend: ~4.8sQ.async: ~6.9s

EXPRESS

app.post('/users', Q.async(function* (req, res) { var user = new User(req.params);

if (yield user.save()) { res.send( JSON.stringify(user) ); } else { res.send(422); } }));

MOCHA

$ mocha --harmony test.js

describe('Some stuff', function() { // ...

it('should do async operation', Y(function* (done) { var a = yield b(); done(); })); });

Асинхронный код

function(args, function(err, res) { function(args2, function(err, res) { function(args3, function(err, res) { // ... }); }); });

Promise

func().then(function(res) { // ... }).then(function(res) { // ... }).then(...);

Генераторы + promise

async(function* () { var res = yield func(); // ... })();