Node.js:有什么技术来编写干净,简单的callback代码?

node.js代码被称为callback意大利面。

什么是最好的技术来克服这个问题,并写在Node.js干净,不复杂,易于理解的callback代码?

看一下Promises: http : //promises-aplus.github.io/promises-spec/

这是一个打算解决这个问题的开放标准。

我使用节点模块'q',它实现了这个标准: https : //github.com/kriskowal/q

简单的使用案例:

var Q = require('q'); 

比如我们有如下的方法:

 var foo = function(id) { var qdef = Q.defer(); Model.find(id).success(function(result) { qdef.resolve(result); }); return (qdef.promise); } 

然后我们可以通过方法链接promise。然后():

 foo(<any-id>) .then(function(result) { // another promise }) .then(function() { // so on }); 

也可以通过以下值创造承诺:

 Q([]).then(function(val) { val.push('foo') }); 

还有更多,请参阅文档。

也可以看看:

有几件事情可以做,以避免'matrioska风格'。

  • 您可以将callback存储到variables中:

     var on_read = function (foo, bar) { // some logic }, on_insert = function (err, data) { someAsyncRead(data, on_read); }; someAsyncInsert('foo', on_insert); 
  • 你可以使用一些有助于这些场景的模块 。

     // Example using funk var funk = require('funk'); for(var i = 0; i < 10; i++) { asyncFunction(i, funk.add(function (data) { this[i] = data; })); } funk.parallel(function () { console.log(this); }); 

我build议1)使用CoffeeScript和2)使用命名的callback和传递之间的状态哈希,而不是嵌套callback或允许参数列表变得很长。 所以,而不是

 var callback1 = function(foo) { var callback2 = function(bar) { var callback3 = function(baz) { doLastThing(foo, bar, baz); } doSomethingElse(bar, callback3); } doSomething(foo, callback2); } someAsync(callback1); 

你可以简单地写

 callback1 = (state) -> doSomething state.foo, callback2 callback2 = (state) -> doSomethingElse state.bar, callback3 callback3 = (state) -> doLastThing state someAsync callback1 

一旦你的doSomethingdoSomethingElsedoLastThing被重写为使用/扩展哈希。 (您可能需要编写额外的外部函数。)

正如你所看到的,这个方法中的代码整齐和线性地读取。 而且由于所有callback都暴露出来,所以unit testing变得更容易。

尝试节点线

https://github.com/kevin0571/node-line

用法:

 var line = require("line"); line(function(next) { obj.action1(param1, function(err, rs) { next({ err: err, rs: rs }); }); }, function(next, data) { if (data.err) { console.error(err); return; } obj.action2(param2, function(err, rs) { if (err) { console.error(err); return; } next(rs); }); }, function(rs) { obj.finish(rs); }); 

大多数情况下,仅使用Twitter OAuth2应用程序示例,使用具有https.request ,Nodejs Express apipath的Kris'Q promise库。 首先尝试用户时间轴GET。 如果401响应,刷新承载令牌然后重试用户时间线。 我不得不使用Q.when当处理承诺,返回另一个承诺(链接)或价值。

  /** * Using Rails-like standard naming convention for endpoints. * GET /things -> index * POST /things -> create * GET /things/:id -> show * PUT /things/:id -> update * DELETE /things/:id -> destroy */ 'use strict'; // var _ = require('lodash'); var http = require('http'); var https = require('https'); var querystring = require('querystring'); var Q = require('q') // Get list of twtimelines exports.index = function(req, res) { var tid = req.query.tid if (tid) { Q.when(reqTimeline(tid, true, res), function(value) { // > value // 404 // > body1 // '{"errors":[{"code":34,"message":"Sorry, that page does not exist."}]}' }) } else { res.json({ errors: [{ message: 'no tid specified in query' }] }); } }; function reqPromise(options, postData) { var deferred = Q.defer() var req = https.request(options, function(res) { // console.log("statusCode: ", res.statusCode); // console.log("headers: ", res.headers); var statusCode = res.statusCode deferred.notify(res) res.on('data', function(d) { //process.stdout.write(d); deferred.notify(d) }).on('end', function() { deferred.resolve(statusCode) }); }); req.on('error', function(e) { console.error(e); deferred.reject(e) }); req.write(postData); req.end(); return deferred.promise } // deferRequest function isIncomingMessage(ot) { return ot instanceof http.IncomingMessage } function isBuffer(ot) { return ot instanceof Buffer } function reqTimeline(screen_name, reqBearerTokenOn401, res) { var optionsUserTimeline = { hostname: 'api.twitter.com', path: '/1.1/statuses/user_timeline.json?' + querystring.stringify({ count: '3', screen_name: screen_name }), method: 'GET', headers: { //'Authorization': 'Bearer ' + JSON.parse(body1).access_token 'Authorization': 'Bearer ' + process.env.BEARER_TOKEN } // headers }; console.log("optionsUserTimeline", optionsUserTimeline) var statusCode; var body1 = new Buffer(''); // default utf8 string buffer ? return reqPromise(optionsUserTimeline, '') .then(function(value) { // done if (reqBearerTokenOn401 && value === 401) { console.log("reqTimeline - requesting bearer token") return reqBearerToken(screen_name, res) } console.log("reqTimeline - done done:", value) res.end() return value }, function(reason) { // error console.log("reqTimeline - error:", body1) }, function(progress) { console.log("reqTimeline - progress:", body1) if (isIncomingMessage(progress)) { body1 = body1.slice(0, 0) // re-set buffer statusCode = progress.statusCode; if (reqBearerTokenOn401 && statusCode === 401) { // readyn for retry } else { res.writeHead(statusCode) } } else if (isBuffer(progress)) { if (reqBearerTokenOn401 && statusCode === 401) { body1 += progress } else { res.write(progress) } } else { throw "reqTimeline - unexpected progress" } }); } // reqTimeline function reqBearerToken(screen_name, res) { var postData = querystring.stringify({ 'grant_type': 'client_credentials' }) var optionsBearerToken = { hostname: 'api.twitter.com', path: '/oauth2/token', method: 'POST', headers: { 'Authorization': 'Basic ' + new Buffer( process.env.CONSUMER_KEY + ":" + process.env.CONSUMER_SECRET ).toString('base64'), 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', 'Content-Length': postData.length } // headers } // console.log("key", process.env.CONSUMER_KEY) // console.log("secret", process.env.CONSUMER_SECRET) // console.log("buf", new Buffer( // process.env.CONSUMER_KEY + ":" + process.env.CONSUMER_SECRET // ).toString()) console.log("optionsBearerToken", optionsBearerToken) var body2 = new Buffer(''); // default utf8 string buffer ? return reqPromise(optionsBearerToken, postData) .then(function(value) { // done console.log("reqBearerToken - done:", body2) if (value === 200) { console.log("reqBearerToken - done done") process.env.BEARER_TOKEN = JSON.parse(body2).access_token; return reqTimeline(screen_name, false, res) } return value }, function(reason) { throw "reqBearerToken - " + reason }, function(progress) { if (isIncomingMessage(progress)) { body2 = body2.slice(0, 0) // reset buffer } else if (isBuffer) { body2 += progress } else { throw "reqBearerToken - unexpected progress" } }); } // reqBearerToken