javascript Node.js:有哪些技术可以编写干净、简单的回调代码?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5265743/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 16:35:11  来源:igfitidea点击:

Node.js: What techniques are there for writing clean, simple callback code?

javascriptnode.js

提问by Duke Dougal

node.js code is known for turning into callback spaghetti.

node.js 代码以变成回调意大利面而闻名。

What are the best techniques for overcoming this problem and writing clean, uncomplex, easy to understand callback code in node.js?

克服这个问题并在 node.js 中编写干净、简单、易于理解的回调代码的最佳技术是什么?

回答by x'ES

Take a look at Promises: http://promises-aplus.github.io/promises-spec/

看看承诺:http: //promises-aplus.github.io/promises-spec/

It is an open standard which intended to solve this issue.

这是一个旨在解决这个问题的开放标准。

I am using node module 'q', which implements this standard: https://github.com/kriskowal/q

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

Simple use case:

简单用例:

var Q = require('q');

For example we have 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 promises by method .then():

然后我们可以通过方法 .then() 链接 promises:

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

It is also possible to creating promise from values like:

也可以从以下值创建承诺:

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

And much more, see docs.

还有更多,请参阅文档。

See also:

也可以看看:

回答by masylum

Several things can be done to avoid the 'matrioska-style'.

可以采取多种措施来避免“俄罗斯套娃式”。

  • You can store callbacks to 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 some modulesthat help in those 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);
    });
    
  • 您可以将回调存储到变量:

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

回答by Trevor Burnham

I'd suggest 1) using CoffeeScriptand 2) using named callbacks and passing state between them in a hash, rather than either nesting callbacks or allowing argument lists to get very long. So instead of

我建议 1) 使用CoffeeScript和 2) 使用命名回调并以散列形式在它们之间传递状态,而不是嵌套回调或允许参数列表变得很长。所以代替

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

you can instead simply write

你可以简单地写

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

once your doSomething, doSomethingElseand doLastThinghave been rewritten to use/extend a hash. (You may need to write extra wrappers around external functions.)

一旦你的doSomethingdoSomethingElse并且doLastThing已经被重写并使用/扩展的哈希值。(您可能需要为外部函数编写额外的包装器。)

As you can see, the code in this approach reads neatly and linearly. And because all callbacks are exposed, unit testing becomes much easier.

如您所见,这种方法中的代码读起来整齐而线性。并且因为所有回调都是公开的,所以单元测试变得更加容易。

回答by Kevin

Try node-line

尝试节点线

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

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

Usage:

用法:

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

回答by user982671

For the most part, working Twitter OAuth2 application-only example, using Kris' Q promise library with https.request, Nodejs Express api route. First attempt user timeline GET. If 401 response, refreshing bearer-token then retry user timeline. I had to use Q.whento handle a promise that returns another promise (chaining) or a value.

在大多数情况下,仅使用 Twitter OAuth2 应用程序示例,使用 Kris 的 Q promise 库和https.requestNodejs Express api 路由。首先尝试用户时间线 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