ROCORE, 生成器,惰性求值,科技进步改变异步编程难题

github: rocore.git oscgit: rocore.oscgit oscpage: coding nodejs async with generator

福利:traceur-compiler 目前想要运行ES6代码的话,可以用google/traceur-compiler将代码转译. 然后静待nodejs0.12. (这个代码是运行前完全编译的,所以不用担心解析性能问题)

单队列多任务自由切换?

如果你喜欢nodejs的干脆直接,又深陷异步回调的泥潭,不如试试ROCORE,一个采用生成器和惰性求值的轻量框架。

ROCORE更像是nodejs里的一个干净独立的模块,提供一组轻便好用性能不错的工具, 不会污染nodejs内核,也不进行任何封装。

任务和队列将是nodejs异步编程的主题,ROCORE提供了3个工具,使代码可以自由的在单队列多任务切换:

  • yield停止当前任务
  • next跳到下一个任务(如果有, 否则跳到最外部)
  • ynext返回到当前任务

下面是一组我最近完成的邮箱找回密码代码,当请求时会检测用户email是否已经注册,然后往用户邮箱发送一个临时会话id。 你会看到,整套代码只有if,没有else,代码完全是扁平的,只有一层。

  • 使用redis作为会话缓存服务器
  • 使用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);
});

更有趣的队列实验?

想要在队列的多任务中来去自如,那么下面的代码很有代表性:

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

更有利的异步任务控制工具?

  • rocore.scc(generator, [callback])
  • rocore.mcc(generator, [callback])

使用这两个工具可以帮助你随时完成任意的异步代码:

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);
});

转载于:https://my.oschina.net/tulayang/blog/362507

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值