Статьи

Изучение node.js: шаг

Я играл с node.js, чтобы сгенерировать некоторые графики из нашего git-репозитория, что фактически означало объединение в кучу команд оболочки для предоставления мне данных репозитория в нужном мне формате.

Я смог сделать это, используя child_process, который поставляется с базовой библиотекой.

Первая версия выглядела так:

var exec = require('child_process').exec, _ = require("underscore");
...
function parseCommitsFromRepository(fn) {
  var gitRepository = "/tmp/core";
  var gitPlayArea = "/tmp/" + new Date().getTime();
 
  exec('cd ' + gitRepository + ' && git reset HEAD', function() {
    exec('git clone ' + gitRepository + ' ' + gitPlayArea, function() {
      exec('cd ' + gitPlayArea + ' && git log --pretty=format:"%H | %ad | %s%d" --date=raw', function(blank, gitEntries) {
        var commits = _(gitEntries.split("\n")).chain()
                        .filter(function(item) { return item != ""; })
                        .map(function(item) { return item.split("|") })
                        .filter(function(theSplit) { return theSplit !== undefined && theSplit[1] !== undefined && theSplit[2] !== undefined; })
                        .map(function(theSplit) {  
                          var date = new Date(theSplit[1].trim().split(" ")[0]*1000);
                          return {message: theSplit[2].trim(), date: date.toDateString(), time : date.toTimeString()}; })
                        .value();			
        fn(commits);
      });		
    });
  });
}

В файле node.js используется модель асинхронного программирования, поэтому большую часть времени мы должны передавать обратные вызовы другим функциям, которые вызываются после завершения асинхронного вычисления.

В этом случае есть более зависимость порядка в parseCommitsFromRepository функции таким образом, что нам нужно , чтобы гнездиться второй вызов EXEC внутри обратного вызова от первого звонка.

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

По мере того, как вы создаете все больше и больше зависимостей порядка между асинхронными функциями, вложение становится больше, и код все больше перемещается в правую часть экрана.

Я натолкнулся на библиотеку Step, которая позволяет вам объединять функции и передавать результаты каждой из них следующей.

Я решил попробовать это в своем коде, и в итоге это выглядело так:

function parseCommitsFromRepository(fn) {	
  var gitRepository = "/tmp/core";
  var gitPlayArea = "/tmp/" + new Date().getTime();	
  Step(
    function getRepositoryUpToDate() { exec('cd ' + gitRepository + ' && git reset HEAD', this); },
    function cloneRepository()       { exec('git clone ' + gitRepository + ' ' + gitPlayArea, this); },
    function getGitEntries()         { exec('cd ' + gitPlayArea + ' && git log --pretty=format:"%H | %ad | %s%d" --date=raw', this); },
    function handleResponse(blank, gitEntries) {
      var commits = _(gitEntries.split("\n")).chain()
                      .filter(function(item) { return item != ""; })
                      .map(function(item) { return item.split("|") })
                      .filter(function(theSplit) { return theSplit !== undefined && theSplit[1] !== undefined && theSplit[2] !== undefined; })
                      .map(function(theSplit) {  
                        var date = new Date(theSplit[1].trim().split(" ")[0]*1000);
                        return {message: theSplit[2].trim(), date: date.toDateString(), time : date.toTimeString()}; })
                      .value();			
      fn(commits);
    }
  );	
}

Интересным побочным эффектом использования этого подхода является то, что мы можем описать, что делает каждый вызов exec, от имени функции, которая его выполняет.

Еще одна приятная вещь в этой библиотеке — то, что я могу легко обернуть эти функции внутри функции ведения журнала, если я хочу видеть на консоли, где процесс получил:

function log(message, fn) {
  return function logMe() {
    console.log(new Date().toString() + ": " + message);
     fn.apply(this, arguments);
  }
}
function parseCommitsFromRepository(fn) {	
  var gitRepository = "/tmp/core";
  var gitPlayArea = "/tmp/" + new Date().getTime();	
  Step(
    log("Resetting repository", function getRepositoryUpToDate() { exec('cd ' + gitRepository + ' && git reset HEAD', this); }),
    log("Cloning repository", function cloneRepository()         { exec('git clone ' + gitRepository + ' ' + gitPlayArea, this); }),
    log("Getting log", function getGitEntries()                  { exec('cd ' + gitPlayArea + ' && git log --pretty=format:"%H | %ad | %s%d" --date=raw', this); }),
    log("Processing log", function handleResponse(blank, gitEntries) {
      var commits = _(gitEntries.split("\n")).chain()
                      .filter(function(item) { return item != ""; })
                      .map(function(item) { return item.split("|") })
                      .filter(function(theSplit) { return theSplit !== undefined && theSplit[1] !== undefined && theSplit[2] !== undefined; })
                      .map(function(theSplit) {  
                        var date = new Date(theSplit[1].trim().split(" ")[0]*1000);
                        return {message: theSplit[2].trim(), date: date.toDateString(), time : date.toTimeString()}; })
                      .value();			
      fn(commits);
    })
  );	
}

Затем я получаю этот вывод при выполнении функции:

Sun Sep 11 2011 23:33:09 GMT+0100 (BST): Resetting repository
Sun Sep 11 2011 23:33:11 GMT+0100 (BST): Cloning repository
Sun Sep 11 2011 23:33:24 GMT+0100 (BST): Getting log
Sun Sep 11 2011 23:33:24 GMT+0100 (BST): Processing log

Есть более крутые способы использования библиотеки Step на странице github — то, что я описал здесь, является лишь очень простым вариантом использования.

С http://www.markhneedham.com/blog/2011/09/11/learning-node-js-step/