tutorial servidor nodebeginner node lado introduccion español ejemplos desarrollo javascript node.js

javascript - servidor - Node.js: ¿Qué técnicas existen para escribir un código de devolución de llamada simple y limpio?



nodebeginner español (5)

Eche un vistazo a Promesas: http://promises-aplus.github.io/promises-spec/

Es un estándar abierto que pretende resolver este problema.

Estoy usando el módulo de nodo ''q'', que implementa este estándar: https://github.com/kriskowal/q

Caso de uso simple:

var Q = require(''q'');

Por ejemplo tenemos métodos como:

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

Entonces podemos encadenar promesas por el método .then ():

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

También es posible crear promesas a partir de valores como:

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

Y mucho más, ver docs.

Ver también:

El código node.js es conocido por convertirse en espagueti de devolución de llamada.

¿Cuáles son las mejores técnicas para superar este problema y escribir un código de devolución de llamada limpio, sin complejos y fácil de entender en node.js?


En su mayor parte, ejemplo de solo trabajo de Twitter OAuth2, utilizando la biblioteca de promesa de Kris con https.request , Nodejs Express api route. Primer intento del usuario en la línea de tiempo GET. Si el 401 responde, actualice el token del portador y vuelva a intentar la línea de tiempo del usuario. Tuve que usar Q.when para manejar una promesa que devuelve otra promesa (encadenamiento) o un valor.

/** * 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


Probar línea de nodo

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

Uso:

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


Se pueden hacer varias cosas para evitar el ''estilo matrioska''.

  • Puede almacenar devoluciones de llamada a variables:

    var on_read = function (foo, bar) { // some logic }, on_insert = function (err, data) { someAsyncRead(data, on_read); }; someAsyncInsert(''foo'', on_insert);

  • Puede utilizar algunos modules que ayudan en esos escenarios.

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


Yo sugeriría 1) usar CoffeeScript y 2) usar devoluciones de llamada con nombre y pasar el estado entre ellas en un hash, en lugar de anidar devoluciones de llamada o permitir que las listas de argumentos sean muy largas. Así que en lugar de

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

en su lugar puedes simplemente escribir

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

una vez que tu doSomething , doLastThing y doLastThing hayan sido reescritos para usar / extender un hash. (Es posible que deba escribir envoltorios adicionales alrededor de funciones externas).

Como puede ver, el código en este enfoque se lee de forma clara y lineal. Y como todas las devoluciones de llamada están expuestas, las pruebas unitarias se vuelven mucho más fáciles.