waitfor, node.js的顺序编程,回调地狱/金字塔的结束

分享于 

12分钟阅读

GitHub

  繁體 雙語
Sequential programming for node.js, end of callback hell / pyramid of doom
  • 源代码名称:waitfor
  • 源代码网址:http://www.github.com/luciotato/waitfor
  • waitfor源代码文档
  • waitfor源代码下载
  • Git URL:
    git://www.github.com/luciotato/waitfor.git
    Git Clone代码到本地:
    git clone http://www.github.com/luciotato/waitfor
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/luciotato/waitfor
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
    Wait.for

    node.js的顺序编程,回调地狱的结束。

    简单的,直接的抽象,超过了光纤。

    使用 wait.for,可以按顺序/同步模式调用任何nodejs标准异步函数,等待结果数据而不阻塞 node 循环( 感谢光纤)

    在nodejs标准异步函数中,最后一个参数是一个回调函数: 函数( 错误,数据)

    优点:

    • 避免回调地狱/金字塔
    • 当需要时,简化顺序编程,不阻塞 node 循环事件( 感谢纤维)
    • 更简单,尝试捕获异常编程。( 默认回调处理程序为: 如果( 错误) 抛出 err ;否则返回数据)
    • 你还可以启动多个并行非并行光纤。
    • 没有多线程调试梦梦,只有一个光纤在给定时间运行( 感谢纤维)
    • 可以将任何节点标准异步函数与 callback(err,data) 作为最后一个参数。
    • node 编程风格一起发挥。 用 callback(err,data), 编写异步函数,但需要时按顺序/同步模式使用它们。
    • node 群集一起播放。 你设计了一个线程/处理器,然后在multicores上使用集群进行缩放。

    ##NEWS

    ###Aug-2013 - 基于ES6-generators的Wait.for-ES6

    我已经在ES6-Harmony即将推出的生成器 开发了一个版本 它不是基于节点纤维。 令人惊讶的是,基于ES6的实现,几乎是无操作的,你甚至可以完全忽略它。 警告:出血。 检查 [Wait.for-ES6] ( https://github.com/luciotato/waitfor-ES6 )

    安装:

    
    npm install wait.for
    
    
    
    

    正确使用:

    你需要在光纤中才能使用 wait.for. 来启动光纤,当请求到达时,处理它:

    var server =http.createServer(
     function(req, res){
     console.log('req!');
     wait.launchFiber(handler,req,res); //handle in a fiber, keep node spinning }).listen(8000);

    然后,在函数 handler(req,res) 和从那里调用的每个函数,你都可以使用等待。例如( ayncfn。

    最小运行示例

    var wait =require('wait.for');functionanyStandardAsync(param, callback){
     setTimeout( function(){
     callback(null,'hi '+param);
     }, 5000);
    };functiontestFunction(){
     console.log('fiber start');
     var result =wait.for(anyStandardAsync,'test');
     console.log('function returned:', result);
     console.log('fiber end');
    };console.log('app start');wait.launchFiber(testFunction);console.log('after launch');

    基本用法示例与 Express.js

    var wait =require('wait.for');var express =require('express');var app =express();// in a FiberfunctionhandleGet(req, res){
     res.send( wait.for(fs.readFile,'largeFile.html') );
    }app.get('/', function(req,res){
     wait.launchFiber(handleGet, req, res); //handle in a fiber, keep node spinning});app.listen(3000);

    cradle/couchdb用法示例

    参见底座示例

    通用用法:

    var wait=require('wait.for');// launch a new fiberwait.launchFiber(my_sequential_function, arg,arg,...)// in a fiber.. We can wait for async functionsfunctionmy_sequential_function(arg,arg...){
     // call async_function(arg1), wait for result, return datavar myObj =wait.for(async_function, arg1); 
     // call myObj.querydata(arg1,arg2), wait for result, return datavar myObjData =wait.forMethod(myObj,'queryData', arg1, arg2);
     console.log(myObjData.toString());
    }

    ##Notes 上的非标准回调 比如: 来自mysql的connection.query,node-sqlite3上的database.prepare

    wait.for 要求标准化回调标准化回调总是按顺序返回( 错误数据)。

    对于 sql.query 方法和其他非标准回调的解决方案是创建一个包装函数标准化回调,e.g.:

    connection.prototype.q=function(sql, params, stdCallback){ 
     this.query(sql,params, function(err,rows,columns){ 
     returnstdCallback(err,{rows:rows,columns:columns}); 
     });
     }

    使用方法:

    try {
     var result =wait.forMethod(connection, "q", options.sql, options.params); 
     console.log(result.rows);
     console.log(result.columns);
    } catch(err) {
     console.log(err);
    }

    比如: node-sqlite3 database.prepare

    var sqlite3 =require('sqlite3').verbose();var db =newsqlite3.Database(':memory:');db.prototype.prep=function(sql, stdCallback){ 
     var stmt =this.prepare(sql, function(err){ 
     returnstdCallback(err, stmt); 
     });
     }var stmt =wait.forMethod (db, 'prep', "INSERT OR REPLACE INTO foo (a,b,c) VALUES (?,?,?)");

    更多示例:

    DNS测试,使用纯 node.js: ( callback回地狱) 进行 :

    var dns =require("dns");functiontest(){ 
     dns.resolve4("google.com", function(err, addresses) {
     if (err) throw err;
     for (var i =0; i <addresses.length; i++) {
     var a = addresses[i];
     dns.reverse(a, function (err, data) {
     if (err) throw err;
     console.log("reverse for "+ a +": "+JSON.stringify(data));
     });
     };
     });
    }test();

    使用 wait.for ( 顺序顺序) 相同代码:

    var dns =require("dns"), wait=require('wait.for');functiontest(){
     var addresses =wait.for(dns.resolve4,"google.com");
     for (var i =0; i <addresses.length; i++) {
     var a = addresses[i];
     console.log("reverse for "+ a +": "+JSON.stringify(wait.for(dns.reverse,a)));
     }
    }wait.launchFiber(test); 

    数据库示例( 伪代码)

    使用纯 node.js ( 回调地狱):

    var db =require("some-db-abstraction");functionhandleWithdrawal(req,res){ 
     try {
     var amount=req.param("amount");
     db.select("* from sessions where session_id=?",req.param("session_id"),function(err,sessiondata) {
     if (err) throw err;
     db.select("* from accounts where user_id=?",sessiondata.user_ID),function(err,accountdata) {
     if (err) throw err;
     if (accountdata.balance< amount) thrownewError('insufficient funds');
     db.execute("withdrawal(?,?)",accountdata.ID,req.param("amount"), function(err,data) {
     if (err) throw err;
     res.write("withdrawal OK, amount: "+req.param("amount"));
     db.select("balance from accounts where account_id=?", accountdata.ID,function(err,balance) {
     if (err) throw err;
     res.end("your current balance is "+balance.amount);
     });
     });
     });
     });
     }
     catch(err) {
     res.end("Withdrawal error: "+err.message);
     }
    }

    注意:尽管 上面 代码看起来会捕捉异常,但将不会导致异常。 使用回调地址捕捉异常会增加很多痛苦,并且我不确定是否有'res'参数响应用户。 如果有人想修复这个例子。 是 跳 到 上.

    使用 wait.for ( 时序逻辑- 顺序程序设计) 相同代码:

    var db =require("some-db-abstraction"), wait=require('wait.for');functionhandleWithdrawal(req,res){ 
     try {
     var amount=req.param("amount");
     sessiondata =wait.forMethod(db,"select","* from session where session_id=?",req.param("session_id"));
     accountdata =wait.forMethod(db,"select","* from accounts where user_id=?",sessiondata.user_ID);
     if (accountdata.balance< amount) thrownewError('insufficient funds');
     wait.forMethod(db,"execute","withdrawal(?,?)",accountdata.ID,req.param("amount"));
     res.write("withdrawal OK, amount: "+req.param("amount"));
     balance =wait.forMethod(db,"select","balance from accounts where account_id=?", accountdata.ID);
     res.end("your current balance is "+balance.amount);
     }
     catch(err) {
     res.end("Withdrawal error: "+err.message);
     }
    }

    注意:异常将按预期捕获。 db方法( db。选择。db。执行) 将用this=db调用

    ##How 运行 wait.launchFiber?

    wait.launchFiber(genFn,param1,param2) 在执行异步命令时,将执行 function genFn/生成器,然后执行"已经生成"值( 对异步函数的调用),然后执行 wait.launchFiber,然后在调用异步 callback(err,data), 时执行/generator"继续",然后调用 var x =wait.for(...)/generator。

    并行扩展

    ###wait.parallel.launch(functions:Array)

    注:必须在纤维中

    ####input:

    • 函数:array = [[func,arg,arg],[func,arg,arg],。]

    wait.parallel.launch 需要一个,[func,arg,arg..],。],然后为每个函数调用启动一个光纤,并等待所有的调用,并等待所有的光纤结束。

    要调用的函数 不应该是异步函数

    每个被称为同步函数都将在它自己的光纤中执行,这个同步函数应该在内部使用 data=wait.for(..) 来调用异步函数。

    ####actions: -launchs每个功能-the光纤的光纤 resultArray[index] = func.apply(undefined,args)

    ####returns:

    • 每个函数的结果为 array
    • 所有纤维全部完成前,不要"返回"。
    • 如果错误抛出

    ###wait.parallel.map(arr:Array, mappedFn:function )

    注:必须在纤维中

    ####input:

    • array:
    • mappedFn = function(item,index,arr) -- mappedFn应返回已经转换的项。 因为我们在一个光纤 -- mappedFn可以使用 wait.for,也可以使用 throw/try/catch

    ####returns:

    • 带转换项的array
    • 所有纤维全部完成前,不要"返回"。
    • 如果错误抛出

    ###wait.parallel.filter(arr:Array, itemTestFn:function )

    注:必须在纤维中

    ####input:

    • array:
    • itemTestFn = function(item,index,arr) -- itemTestFn应返回 true|false。 因为我们在一个光纤 -- itemTestFn可以使用 wait.for,也可以使用 throw/try/catch

    ####returns

    • 带有 itemTestFn() 返回 true的项的array
    • 所有纤维全部完成前,不要"返回"。
    • 如果错误抛出

    并行用法示例:请参阅:


    CAL  调用  SEQ  回调  Sequential  Pyramid