Node.js: What are the methods for writing clean, simple callback code?

node.js is known to turn into callback spaghetti.

What are the best methods to overcome this problem and write clean, non-complex, easy to understand callback code in node.js?

+27
javascript
Mar 10 2018-11-11T00:
source share
5 answers

Take a look at Promises: http://promises-aplus.imtqy.com/promises-spec/

This is an open standard designed to solve this problem.

I use the node module 'q' which implements this standard: https://github.com/kriskowal/q

A simple option:

var Q = require('q'); 

For example, we have a method like:

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

Then we can chain the promises using the .then () method:

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

You can also create a promise from values ​​such as:

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

And much more, see the docs.

See also:

+8
25 Oct '13 at 0:19
source share

Several things can be done to avoid the "matrioski style".

  • You can save callbacks for variables:

     var on_read = function (foo, bar) { // some logic }, on_insert = function (err, data) { someAsyncRead(data, on_read); }; someAsyncInsert('foo', on_insert); 
  • You can use modules to help in these scenarios.

     // 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); }); 
+4
Mar 11 '11 at 11:51
source share

I would suggest 1) using CoffeeScript and 2) using named callbacks and the transfer state between them in a hash, rather than nested callbacks or providing argument lists for a very long time. Therefore, instead of

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

instead, you can simply write

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

once your doSomething , doSomethingElse and doLastThing have been rewritten to use / extend the hash. (You may need to write additional wrappers around external functions.)

As you can see, the code in this approach is read accurately and linearly. And since all callbacks are displayed, unit testing becomes much easier.

+2
Mar 14 '11 at 16:20
source share

Try node -line

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

Using:

 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); }); 
0
Jan 30 '15 at 7:05
source share

For the most part, working in an OAuth2 Twitter-only app using Chris Q's promise library with https.request , the Nodejs Express api route. The first attempt at a custom GET timeline. If the answer is 401, updating the carrier token, try the user again. I had to use Q.when to fulfill a promise that returns another promise (chain) or value.

  /** * 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 
0
Sep 14 '15 at 19:59
source share



All Articles