压扁_very_嵌套密集function:如何?

我是JsonRestStores的作者。 我一直推迟这个问题太久了。 这是一个可以赢得“年度最愚蠢缩进function”奖的function。

主要的粘性问题是closuresvariables有一点变化:

body[ self.idProperty ] = params[ self.idProperty ]; 

还有一个“如果”使事情变得有趣。

所以…有没有一种优雅的方式来把这个function变成一个看起来不像箭头那样的两个戳? 如果是这样,你能提供一个示例实现?

  _makePostAppend: function( params, body, options, next ){ var self = this; var body; if( typeof( next ) !== 'function' ) next = function(){}; // Check that the method is implemented if( ! self.handlePostAppend ){ self._sendError( next, new self.NotImplementedError( ) ); return; } // Check the IDs self._checkParamIds( params, body, false, function( err ){ self._sendErrorOnErr( err, next, function(){ self.schema.validate( body, function( err, body, errors ) { self._sendErrorOnErr( err, next, function(){ if( errors.length ){ self._sendError( next, new self.UnprocessableEntityError( { errors: errors } ) ); } else { // Fetch the doc self.execAllDbFetch( params, body, options, function( err, fullDoc ){ self._sendErrorOnErr( err, next, function(){ self.extrapolateDoc( params, body, options, fullDoc, function( err, doc) { self._sendErrorOnErr( err, next, function(){ self._castDoc( doc, function( err, doc) { self._sendErrorOnErr( err, next, function(){ // Actually check permissions self.checkPermissionsPostAppend( params, body, options, doc, fullDoc, function( err, granted ){ self._sendErrorOnErr( err, next, function(){ if( ! granted ){ self._sendError( next, new self.ForbiddenError() ); } else { // Clean up body from things that are not to be submitted //if( self.schema ) self.schema.cleanup( body, 'doNotSave' ); self.schema.cleanup( body, 'doNotSave' ); // Paranoid check // Make sure that the id property in the body does match // the one passed as last parameter in the list of IDs body[ self.idProperty ] = params[ self.idProperty ]; self.execPostDbAppend( params, body, options, doc, fullDoc, function( err, fullDocAfter ){ self._sendErrorOnErr( err, next, function(){ self.extrapolateDoc( params, body, options, fullDocAfter, function( err, doc) { self._sendErrorOnErr( err, next, function(){ self._castDoc( fullDocAfter, function( err, docAfter) { self._sendErrorOnErr( err, next, function(){ // Remote request: set headers, and send the doc back (if echo is on) if( self.remote ){ if( self.echoAfterPostAppend ){ self.prepareBeforeSend( docAfter, function( err, docAfter ){ self._sendErrorOnErr( err, next, function(){ self.afterPostAppend( params, body, options, doc, fullDoc, docAfter, fullDocAfter, function( err ){ self._sendErrorOnErr( err, next, function(){ self._res.json( 200, docAfter ); }); }); }) }) } else { self.afterPostAppend( params, body, options, doc, fullDoc, docAfter, fullDocAfter, function( err ){ self._sendErrorOnErr( err, next, function(){ self._res.send( 204, '' ); }); }); } // Local request: simply return the doc to the asking function } else { self.prepareBeforeSend( docAfter, function( err, docAfter ){ self._sendErrorOnErr( err, next, function(){ self.afterPostAppend( params, body, options, doc, fullDoc, docAfter, fullDocAfter, function( err ){ self._sendErrorOnErr( err, next, function(){ next( null, docAfter, self.idProperty ); }) }) }) }) } }) }); }); }) }) // err }) // execPostDbAppend } // granted }) }) }) }) }) }) }) // err }) // checkPermissionsPostAppend } // errors.length }) // err }) // self.validate }) // err }) // self.validate }, 

如果我正在编写像你的代码,我宁愿使用asynchronous库,它是瀑布函数,所以我不必包装与承诺版本的asynchronousAPI。 这非常简单。 承诺也很好,@ Esailija的答案没有任何问题,但是我个人认为这样做更容易实现,并且是可读的:

 var async = require('async'); var _makePostAppend = function (params, body, options, next) { var self = this, body; if (typeof(next) !== 'function') next = function () { }; // Check that the method is implemented if (!self.handlePostAppend) { self._sendError(next, new self.NotImplementedError()); return; } async.waterfall([ function (cb) { // Check the IDs self.checkParamIds(params, body, false, cb); }, function (cb) { self.schema.validate(body, cb); }, function (body, errors, cb) { if (errors.length) cb(new self.UnprocessableEntityError({ errors: errors })); // Fetch the doc self.execAllDbFetch(params, body, options, cb); }, function (fullDoc, cb) { self.extrapolateDoc(params, body, options, fullDoc, function (err, doc) { cb(err, fullDoc, doc); }); }, function (fullDoc, doc, cb) { self._castDoc(doc, function (err, doc) { cb(err, fullDoc, doc); }); }, function (fullDoc, doc, cb) { // Actually check permissions self.checkPermissionsPostAppend(params, body, options, doc, fullDoc, function (err, granted) { cb(err, fullDoc, doc, granted); }); }, function (fullDoc, doc, granted, cb) { if (!granted) cb(new self.ForbiddenError()); // Clean up body from things that are not to be submitted //if( self.schema ) self.schema.cleanup( body, 'doNotSave' ); self.schema.cleanup(body, 'doNotSave'); // Paranoid check // Make sure that the id property in the body does match // the one passed as last parameter in the list of IDs body[self.idProperty] = params[self.idProperty]; self.execPostDbAppend(params, body, options, doc, fullDoc, function (err, fullDocAfter) { cb(err, fullDoc, fullDocAfter); }); }, function (fullDoc, fullDocAfter, cb) { self.extrapolateDoc(params, body, options, fullDocAfter, function (err, doc) { cb(err, fullDoc, doc, fullDocAfter); }); }, function (fullDoc, doc, fullDocAfter, cb) { self._castDoc(fullDocAfter, function (err, docAfter) { cb(err, fullDoc, doc, fullDocAfter, docAfter); }); } ], function (err, fullDoc, doc, fullDocAfter, docAfter) { self._sendErrorOnErr(err, next, function () { // Remote request: set headers, and send the doc back (if echo is on) if (self.remote) { if (self.echoAfterPostAppend) { async.waterfall([ function (cb) { self.prepareBeforeSend(docAfter, cb); }, function (docAfter, cb) { self.afterPostAppend(params, body, options, doc, fullDoc, docAfter, fullDocAfter, cb) } ], function (err, docAfter) { self._sendErrorOnErr(err, next, function () { self._res.json(200, docAfter); }); }); } else { self.afterPostAppend(params, body, options, doc, fullDoc, docAfter, fullDocAfter, function (err) { self._sendErrorOnErr(err, next, function () { self._res.send(204, ''); }); }); } // Local request: simply return the doc to the asking function } else { async.waterfall([ function (cb) { self.prepareBeforeSend(docAfter, function (err, docAfter) { cb(err, doc, fullDoc, fullDocAfter, docAfter); }) }, function (doc, fullDoc, fullDocAfter, docAfter, cb) { self.afterPostAppend(params, body, options, doc, fullDoc, docAfter, fullDocAfter, function (err) { cb(err, docAfter); }); } ], function (err, docAfter) { self._sendErrorOnErr(err, next, function () { next(null, docAfter, self.idProperty); }); }); } }); }); }; 

或者更好的是,我使用@ Esailija的回答的范围欺骗:

 var async = require('async'); var _makePostAppend = function (params, body, options, next) { var _self = this, _body, _fullDoc, _doc, _docAfter, _fullDocAfter; if (typeof(next) !== 'function') next = function () { }; // Check that the method is implemented if (!_self.handlePostAppend) { _self._sendError(next, new _self.NotImplementedError()); return; } async.waterfall([ function (cb) { // Check the IDs _self.checkParamIds(params, _body, false, cb); }, function (cb) { _self.schema.validate(_body, cb); }, function (body, errors, cb) { if (errors.length) cb(new _self.UnprocessableEntityError({ errors: errors })); // Fetch the doc _self.execAllDbFetch(params, body, options, cb); }, function (fullDoc, cb) { _fullDoc = fullDoc; _self.extrapolateDoc(params, _body, options, fullDoc, db); }, function (doc, cb) { _self._castDoc(doc, cb); }, function (doc, cb) { _doc = doc; // Actually check permissions _self.checkPermissionsPostAppend(params, _body, options, doc, _fullDoc, cb); }, function (granted, cb) { if (!granted) cb(new _self.ForbiddenError()); // Clean up body from things that are not to be submitted //if( self.schema ) self.schema.cleanup( body, 'doNotSave' ); _self.schema.cleanup(_body, 'doNotSave'); // Paranoid check // Make sure that the id property in the body does match // the one passed as last parameter in the list of IDs _body[_self.idProperty] = params[_self.idProperty]; _self.execPostDbAppend(params, _body, options, _doc, _fullDoc, cb); }, function (fullDocAfter, cb) { _fullDocAfter = fullDocAfter; _self.extrapolateDoc(params, _body, options, fullDocAfter, cb); }, function (doc, cb) { _doc = doc; _self._castDoc(_fullDocAfter, cb); } ], function (err, docAfter) { _self._sendErrorOnErr(err, next, function () { // Remote request: set headers, and send the doc back (if echo is on) if (_self.remote) { if (_self.echoAfterPostAppend) { async.waterfall([ function (cb) { _self.prepareBeforeSend(docAfter, cb); }, function (docAfter, cb) { _self.afterPostAppend(params, _body, options, _doc, _fullDoc, docAfter, _fullDocAfter, cb) }, function (cb) { _self._res.json(200, docAfter); cb(); } ], function (err, results) { _self._sendErrorOnErr(err, next); }); } else { _self.afterPostAppend(params, _body, options, _doc, _fullDoc, docAfter, _fullDocAfter, function (err) { _self._sendErrorOnErr(err, next, function () { _self._res.send(204, ''); }); }); } // Local request: simply return the doc to the asking function } else { async.waterfall([ function (cb) { _self.prepareBeforeSend(docAfter, cb); }, function (docAfter, cb) { _docAfter = docAfter; _self.afterPostAppend(params, _body, options, _doc, _fullDoc, docAfter, _fullDocAfter, cb); } ], function (err) { _self._sendErrorOnErr(err, next, function () { next(null, _docAfter, _self.idProperty); }); }); } }); }); }; 

Promises允许您直接从同步代码编写asynchronous代码,因为它们可以恢复exception冒泡和返回值组合。

假设你已经重写了其他的方法来承诺:

 var Promise = require("bluebird"); ... _makePostAppend: function (params, body, options) { var fullDoc, doc, docAfter, fullDocAfter; // Check that the method is implemented if (!this.handlePostAppend) { return Promise.rejected(new this.NotImplementedError()); } //Note that it is Promise#bind, not Function#bind return this._checkParamIds(param, body, false).bind(this).then(function () { return this.schema.validate(body); }).then(function () { return this.execAllDbFetch(params, body, options); }).then(function (_fullDoc) { fullDoc = _fullDoc; return this.extrapolateDoc(params, body, options, fullDoc); }).then(function (doc) { return this._castDoc(doc); }).then(function (_doc) { doc = _doc; return this.checkPermissionsPostAppend(params, body, options, doc, fullDoc); }).then(function (granted) { if (!granted) throw new this.ForbiddenError(); this.schema.cleanup(body, 'doNotSave'); body[this.idProperty] = params[this.idProperty]; return this.execPostDbAppend(params, body, options, doc, fullDoc); }).then(function (_fullDocAfter) { fullDocAfter = _fullDocAfter; return this.extrapolateDoc(params, body, options, fullDocAfter); }).then(function (doc) { return this._castDoc(fullDoc); }).then(function (_docAfter) { docAfter = _docAfter; if (this.remote) { if (this.echoAfterPostAppend) { return this.prepareBeforeSend(docAfter).bind(this).then(function (_docAfter) { docAfter = _docAfter; return this.afterPostAppend(params, body, options, doc, fullDoc, docAfter, fullDocAfter); }).then(function () { return this._res.json(200, docAfter); }); } else { return this.afterPostAppend(params, body, options, doc, fullDoc, docAfter, fullDocAfter).bind(this).then(function () { return this._res.send(204, ''); }); } } else { return this.prepareBeforeSend(docAfter).then(function (_docAfter) { docAfter = _docAfter; return this.afterPostAppend(params, body, options, doc, fullDoc, docAfter, fullDocAfter); }); } }); } 

请注意,您不再需要使用2空格缩进作弊,上面的4空格缩进将更具可读性。 也许这只是我。

用法是:

 this._makePostAppend(params, body, options).bind(this).then(function() { }).catch(this.UnprocessableEntityError, function(e) { }).catch(this.NotImplementedError, function(e) { }).catch(this.ForbiddenError, function(e) { }).catch(function(e) { //Any other error }); 

回拨地狱是一个可怕的地方:)

你可以使用像Step.js这样的callback一个更可读的步骤序列。 还有很多其他的asynchronouspipe理库,但是我不确定这会真的把你保存在这里…你仍然会有一堆乱七八糟的东西,它不会被大大缩减。


我build议你停止思考程序,并开始考虑你的数据模型,什么对象有什么方法,什么方法负责什么primefaces任务。

所以我会简单地重构,就像我会过度肥胖的方法一样:将相关的代码抽象成自己的方法。

 self.checkIds(function() { self.fetchDoc(function() { self.checkPermissions({ deny: self.denyPermission, allow: function() { // call method that handles the next thing } }) }) }); 

现在你有一个macros方法,它只是简单地调用实际工作发生的组件方法。 这些方法中的每一个都可能在内部有一些callback,然后调用你给它的任何“全部完成”callback,将控制权交还给你的macros函数。

这还有一个额外的好处,就是能够根据实际发生的事情来阅读和理解大量的步骤。 它像现在一步一步的指令,而不是一个低级别的噪音ginormous堆。

所以做十几个更小的方法,完成一小组非常简单的asynchronous事件和callback。 并且一定要描述性地命名它们。 然后把它们连接在一个更小,更易维护的callback树中。

如果这个大方法没有评论意义,你会知道你接近一个更好的版本。

祝你好运,你有这个工作切断你的工作。


请注意,你也有一些常见的模式,所以你重复自己!

像这儿:

 self.extrapolateDoc( params, body, options, fullDocAfter, function( err, doc) { self._sendErrorOnErr( err, next, function(){ 

和这里:

 self.afterPostAppend( params, body, options, doc, fullDoc, docAfter, fullDocAfter, function( err ){ self._sendErrorOnErr( err, next, function(){ 

你可以编写一个接受方法名和参数列表的方法,并且在第一个方法callback时通过self._sendErrorOnErr()自动推送。

find更多的常见模式,你可以修剪这更多。

这个问题有点难以解答,但希望至less有一点亮点。 我要做的第一件事是简化代码本身的同步版本。 你从这样的事情开始:

 _makePostAppend: function (params, body, options) { // ...and in the darkness, bind them (if needed) _.bindAll(this, 'checkParamIds', 'execAllDbFetch'); // ...etc. // Shortcuts. These would be unnecessary for private methods in a closure var checkParamIds = this.checkParamIds, schema = this.schema, execAllDbFetch = this.execAllDbFetch, execPostDbAppend = this.execPostDbAppend, extrapolateDoc = this.extrapolateDoc, checkPermissionsPostAppend = this.checkPermissionsPostAppend, _castDoc = this._castDoc, UnprocessableEntityError = this.UnprocessableEntityError, ForbiddenError = this.ForbiddenError, idProperty = this.idProperty, remote = this.remote, echoAfterPostAppend = this.echoAfterPostAppend, prepareBeforeSend = this.prepareBeforeSend, afterPostAppend = this.afterPostAppend, _res = this._res; checkParamIds(params, body, false); // This could throw the UnprocessableEntityError itself var errors = schema.validate(body); if (errors.length) { throw new UnprocessableEntityError({ errors: errors }); } // Every method takes params+body+options. Wrap that in a single object? Alternatively: // var processor = this.getDocProcessor(params, body, options); // var fullDoc = processor.execAllDbFetch(); // var doc = extrapolateDoc(fullDoc); // ...etc. var fullDoc = execAllDbFetch(params, body, options); var doc = extrapolateDoc(params, body, options, fullDoc); doc = _castDoc(doc); // This could throw the ForbiddenException itself var granted = checkPermissionsPostAppend(params, body, options, doc, fullDoc); if (!granted) { throw new ForbiddenError(); } schema.cleanup(body, 'doNotSave'); body[idProperty] = params[idProperty]; var fullDocAfter = execPostDbAppend(params, body, options, doc, fullDoc); var docAfter = extrapolateDoc(params, body, options, fullDocAfter); docAfter = _castDoc(docAfter); if (!remote || echoAfterPostAppend) { docAfter = prepareBeforeSend(docAfter); } afterPostAppend(params, body, options, doc, fullDoc, docAfter, fullDocAfter); return remote ? _res.json(200, echoAfterPostAppend ? docAfter : '') : docAfter; } 

现在,如果你有生成器,你可以简单地用yield来作为asynchronous方法的前缀,然后把它传递给剩下的任何库函数(Q.star,我认为是其中之一)。 你需要promisify async方法,或者至less绑定它们的input(即func(in1,in2)变成apply(func,in1,in2)),但是这样你最终得到的东西看起来非常相似普通的旧同步代码。

不过,你可能没有发电机,所以有一个不太好的答案。 较less的意思是指我从一个稍微简化的变体开始,如上面的注释所述。 也就是说,同步代码如下所示:

 // Helper method. Could be private, defined elsewhere, etc. var extrapolateCast = function (fullDoc) { var doc = extrapolateDoc(fullDoc); return _castDoc(doc); }; _makePostAppend: function (params, body, options) { checkParamIds(params, body, false); schema.validate(body); var proc = getDocProcessor(params, body, options); var fullDoc = proc.execAllDbFetch(); var doc = extrapolateCast(fullDoc); proc.checkPermissionsPostAppend(doc, fullDoc); schema.cleanup(body, 'doNotSave'); body[idProperty] = params[idProperty]; var fullDocAfter = proc.execPostDbAppend(doc, fullDoc); var docAfter = extrapolateCast(fullDocAfter); if (!remote || echoAfterPostAppend) { docAfter = prepareBeforeSend(docAfter); } proc.afterPostAppend(doc, fullDoc, docAfter, fullDocAfter); return remote ? _res.json(200, echoAfterPostAppend ? docAfter : '') : docAfter; } 

二十行左右,更多的可pipe理性。 使用asynchronous,使用上下文对象的解决scheme可能如下所示。 (是的,我知道这在许多方面都很糟糕,但是我已经花了很多时间了。)编辑:这实际上不起作用,来自上下文的variables将被立即解决,而不仅仅是在执行之前。 你可以把它们包装在一个函数中,使它延迟,或者实现一个类似于'save'的'load'函数。

 _makePostAppend: function (params, body, options) { // Helper method. Could be private, defined elsewhere, etc. var extrapolateCast = pipeline( extrapolateDoc, _castDoc ); var vars = {}, contextObj = context(vars), save = contextObj.save, record = contextObj.record, proc = getDocProcessor(params, body, options); pipeline( apply(checkParamIds, params, body, false), apply(schema.validate, body), record('fullDoc', proc.execAllDbFetch), save('doc', extrapolateCast), apply(proc.checkPermissionsPostAppend, vars.doc, vars.fullDoc), apply(schema.cleanup, body, 'doNotSave'), lift(function () { body[idProperty] = params[idProperty]; }), record('fullDocAfter', apply(proc.execPostDbAppend, vars.doc, vars.fullDoc)), record('docAfter', extrapolateCast), conditional( function () { return !remote || echoAfterPostAppend; }, prepareBeforeSend ), apply(proc.afterPostAppend, vars.doc, vars.fullDoc, vars.docAfter, vars.fullDocAfter) ); // NOTE: Converting this part is left as an exercise for the reader. (read: I'm tired.) return remote ? _res.json(200, echoAfterPostAppend ? docAfter : '') : docAfter; }; 

你看到的帮手方法如下:

 var context = function (store) { return { save: function (key, task) { return pipeline( task, tap(lift(function (result) { store[key] = result; })) ); }, // Same as save, but discards result record: function (key, task) { return pipeline( task, liftM(function (result) { store[key] = result; return []; }) ); } }; }, conditional = function (cond, task) { // NOTE: Too tired to figure this out now. Sorry! }, createTask = function (impl) { return function () { var args = _.initial(arguments), callback = _.last(arguments); async.nextTick(function () { impl.call(null, args, callback); }); }; }, liftM = function (fun) { return createTask(function (args, callback) { try { var results = fun.apply(this, args); return callback.apply(this, [null].concat(results)); } catch (err) { return callback.call(this, err); } }); }, lift = function (fun) { return createTask(function (args, callback) { try { var result = fun.apply(this, args); return callback(null, result); } catch (err) { return callback(err); } }); }, tap = function (interceptor) { return createTask(function (args, callback) { return async.waterfall([ _.partialArr(interceptor, args), liftM(function () { return args; }) ], callback); }); };