github: rocore.git oscgit: rocore.oscgit oscpage: coding nodejs async with generatorhtml
福利:traceur-compiler 目前想要運行ES6代碼的話,能夠用google/traceur-compiler將代碼轉譯. 而後靜待nodejs0.12. (這個代碼是運行前徹底編譯的,因此不用擔憂解析性能問題)node
若是你喜歡nodejs的乾脆直接,又深陷異步回調的泥潭,不如試試ROCORE,一個採用生成器和惰性求值的輕量框架。git
ROCORE更像是nodejs裏的一個乾淨獨立的模塊,提供一組輕便好用性能不錯的工具, 不會污染nodejs內核,也不進行任何封裝。github
任務和隊列將是nodejs異步編程的主題,ROCORE提供了3個工具,使代碼能夠自由的在單隊列多任務切換:redis
下面是一組我最近完成的郵箱找回密碼代碼,當請求時會檢測用戶email是否已經註冊,而後往用戶郵箱發送一個臨時會話id。 你會看到,整套代碼只有if,沒有else,代碼徹底是扁平的,只有一層。mongodb
路由文件:編程
// route.js let R = require('rocore'); let userdb = require('../database/user'); let session = require('../session'); let validate = require('./validate'); let cparser = require('../cookie-parser'); let nodemailer = require('nodemailer'); const FIND_MAIL_HOST = 'smtp.163.com'; const FIND_MAIL_PORT = 465; const FIND_MAIL_USER = 'wtaaa@163.com'; const FIND_MAIL_PWD = 'abc123'; const FIND_MAIL_FROM = 'wtaaa@163.com'; const FIND_MAIL_SUBJECT = 'Hello ✔'; const FIND_MAIL_BODY = '<h3>Hello world ✔</h3><p><a href="http://www.baidu.com/userback/{sid}">get back password</a></p>'; exports.send_mail = function* (ynext, next, req, res, mdb, rlci) { let email = req.body.email; let vd_email = validate.vemail(email); // invalid email if (!vd_email) { res.writeHead(200, 'OK', { 'content-type':'text/plain' }); res.end('invalid email'); yield next(); } // let yctx_ue = yield userdb.exists_bye(mdb, email, ynext); let yctx_ue = yield mdb.collection('users').findOne({ email:email }, { fields:{_id:1} }, ynext); let err_ue = yctx_ue[0]; let doc_ue = yctx_ue[1]; // mongo-server error if (err_ue) { res.writeHead(500); res.end('server error'); yield next(); } // email not exists if (!doc_ue) { res.writeHead(200, 'OK', { 'content-type':'text/plain' }); res.end('email not exists'); yield next(); } let transporter = nodemailer.createTransport({ host: FIND_MAIL_HOST, secure: true, // 使用 SSL port: FIND_MAIL_PORT, // SMTP 端口 auth: { user: FIND_MAIL_USER, pass: FIND_MAIL_PWD } }); let yctx_sm = yield transporter.sendMail({ from : FIND_MAIL_FROM, to : email, subject : FIND_MAIL_SUBJECT, html : FIND_MAIL_BODY.replace('{sid}', 'SID123456789ABCDEFGHIJKLMN') }, ynext); let err_sm = yctx_sm[0]; let info_sm = yctx_sm[1]; // send error if (err_sm) { res.writeHead(500); res.end('error'); yield next(); } // successful res.writeHead(200, 'OK'); res.end('OK'); //console.log('Message sent: ' + info_sm.response); yield next(); };
單元測試:緩存
let assert = require('assert'); let R = require('rocore'); let http = require('http'); let url = require('url'); let qs = require('querystring'); let uroute = require('../../lib/route/user'); let cparser = require('../../lib/cookie-parser'); let rcli = require('redis').createClient(); let MongoClient = require('mongodb').MongoClient; let server = http.createServer(); let app = R.Application(); let mdb = null; server .on('request', function (req, res) { app.match(req, res); }) ; app .on('found', function (route, req, res) { req.cookie = cparser.parse(req.headers.cookie); var data = ''; req.setEncoding('utf8'); req.on('data', function (d) { data += d; }); req.on('end', function () { req.body = qs.parse(data, '&', '='); app.exec(route, req, res, mdb, rcli); }); }) .post('/join', uroute.join) .post('/userback', uroute.send_mail) ; R.scc(function* (ynext) { let yctx_mc = yield MongoClient.connect('mongodb://127.0.0.1:31000/test', { "poolSize":10 }, ynext); let err_mc = yctx_mc[0]; if (err_mc) { throw err_mc; } mdb = yctx_mc[1]; server.listen(8000); // join user with full info let res_jf = (yield R.request({ hostname: '127.0.0.1', port: 8000, path: '/join', headers: { 'Content-Type': 'application/application/x-www-form-urlencoded' }, method: 'post', body: { username: 'wt', password: '123456', password2: '123456', email: 'wtaaa@163.com', sex: 'male', language: 'en' } }, ynext))[0]; let res_ue = (yield R.request({ hostname: '127.0.0.1', port: 8000, path: '/userback', headers: { 'Content-Type': 'application/application/x-www-form-urlencoded' }, method: 'post', body: { email: 'wtaaa@163.com' } }, ynext))[0]; assert.strictEqual(res_ue.body, 'OK'); process.exit(0); });
遵循nodejs設計思想,rocore.Application提供事件註冊的機制,服務器配置將會以下:服務器
let R = require('rocore'); let http = require('http'); let url = require('url'); let qs = require('querystring'); let uroute = require('../../lib/route/user'); let cparser = require('../../lib/cookie-parser'); let rcli = require('redis').createClient(); let MongoClient = require('mongodb').MongoClient; let server = http.createServer(); let app = R.Application(); let mdb = null; server .on('request', function (req, res) { app.match(req, res); }) ; app .on('found', function (route, req, res) { req.cookie = cparser.parse(req.headers.cookie); var data = ''; req.setEncoding('utf8'); req.on('data', function (d) { data += d; }); req.on('end', function () { req.body = qs.parse(data, '&', '='); app.exec(route, req, res, mdb, rcli); }); }) .on('notfound', function (req, res) { res.writeHead(404); res.end('Could not found ' + url.parse(req.url).pathname); }) .get('/', function* (ynext, next, req, res) { res.writeHead(200, 'OK', { 'Content-Type':'text/html' }); res.end('/'); }) .post('/join', uroute.join) .post('/login', uroute.login) .post('/logout', uroute.is_login, uroute.logout) ; R.scc(function* (ynext) { let yctx_mc = yield MongoClient.connect('mongodb://127.0.0.1:31000/test', { "poolSize":10 }, ynext); let err_mc = yctx_mc[0]; if (err_mc) { throw err_mc; } mdb = yctx_mc[1]; server.listen(8000); });
想要在隊列的多任務中來去自如,那麼下面的代碼頗有表明性:cookie
let app = require('../lib/rocore').Application(); let assert = require('assert'); let stack = []; function test(x, callback) { process.nextTick(function () { callback(x); }); } app .on('found', function (route, req, res) { app.exec(route, req, res); }) .on('finish', function (ynext, req, res) { if (typeof ynext === 'function') { ynext({ '0': 0 }); } }) .post( '/:user/ttt', function* (ynext, next, req, res) { let a = yield test(1, ynext); stack.push(a[0]); let b = yield next(ynext); stack.push(b[0][0]); let c = yield test(2, ynext); stack.push(c[0]); assert.deepEqual(stack, [ 1, 3, 5, 0, 6, 6, 4, 4, 2 ]); process.exit(0); }, function* (ynext, next, req, res) { let a = yield test(3, ynext); stack.push(a[0]); let b = yield next(ynext); stack.push(b[0][0]); let c = yield test(4, ynext); stack.push(c[0]); }, function* (ynext, next, req, res) { let a = yield test(5, ynext); stack.push(a[0]); let b = yield next(ynext); stack.push(b[0][0]); let c = yield test(6, ynext); stack.push(c[0]); } ) ; // 1, 3, 5, 0, 6, 6, 4, 4, 2
使用這兩個工具能夠幫助你隨時完成任意的異步代碼:
let assert = require('assert'); let R = require('../lib/rocore'); function fA(a, callback) { setTimeout(function () { callback(a, 'aaa'); }, 1000); } function fB(b, callback) { setTimeout(function () { callback(b, 'bbb'); }, 1000); } R.scc(function* (ynext) { let A = yield fA('a1', ynext); assert.strictEqual(A[0], 'a1'); assert.strictEqual(A[1], 'aaa'); let B = yield fB('b1', ynext); assert.strictEqual(B[0], 'b1'); assert.strictEqual(B[1], 'bbb'); let C = yield R.mcc(function* (ynext) { yield fA('a2', ynext('a')); yield fB('b2', ynext('b')); }, ynext); assert.strictEqual(C[0]['a'][0], 'a2'); assert.strictEqual(C[0]['a'][1], 'aaa'); assert.strictEqual(C[0]['b'][0], 'b2'); assert.strictEqual(C[0]['b'][1], 'bbb'); process.exit(0); });