Node.js: Какие существуют методы для написания чистого, простого кода обратного вызова? - PullRequest
27 голосов
/ 10 марта 2011
Код

node.js известен тем, что превращается в спагетти обратного вызова.

Каковы наилучшие методы для преодоления этой проблемы и написания простого, понятного и понятного кода обратного вызова в файле node.js?

Ответы [ 5 ]

8 голосов
/ 25 октября 2013

Посмотрите на Обещания: 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);
}

Тогда мы можем связать обещания методом .then ():

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

Также возможно создать обещание из таких значений, как:

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

И многое другое, см. Документы.

Смотри также:

4 голосов
/ 11 марта 2011

Можно сделать несколько вещей, чтобы избежать «стиля матриоски».

  • Вы можете сохранять обратные вызовы для переменных:

    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);
    });
    
2 голосов
/ 14 марта 2011

Я бы предложил 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);

вместо этого вы можете просто написать

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

после того как ваши doSomething, doSomethingElse и doLastThing были переписаны для использования / расширения хэша. (Возможно, вам понадобится написать дополнительные оболочки для внешних функций.)

Как видите, код в этом подходе читается аккуратно и линейно. А поскольку все обратные вызовы доступны, модульное тестирование становится намного проще.

0 голосов
/ 14 сентября 2015

По большей части работающий пример приложения Twitter OAuth2, использующий библиотеку обещаний Криса Q с https.request, маршрут API Nodejs Express.Первая попытка пользователя временной шкалы 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
0 голосов
/ 30 января 2015

Попробуйте строку узла

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);
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...