2013-12-14 3 views
2

Я пытаюсь написать программу Node, которая заполняет мою базу данных MySQL данными из файлов, которые у меня есть на диске. Я могу или не могу так поступить правильно, но он работает. У меня возникают проблемы с пониманием того, как я должен работать, позволяя завершить асинхронные функции до завершения соединения с БД. В конечном счете, я буду читать множество файлов данных и вставлять их в базу данных, как я сделал ниже. Я могу просто использовать readFileSync вместо асинхронной версии, но мне нужно получить лучший дескриптор асинхронных функций.Узел и MySQL: не удается завершить соединение -> Async confusion

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

connection.connect(function(err) { 
    if(err) { 
     console.log(err); 
    } 
}); 

// Take a table and the values, and insert a new row into a table 
function insert_into(table, values) { 
    if(values instanceof Array) { 
     values = values.map(function(value) { 
      return '"' + value + '"'; 
     }).join(', '); 
    } else { 
     values = '"' + values + '"'; 
    } 

    var statement = 'INSERT INTO ' + table + ' VALUES (NULL, ' + values + ')'; 
    connection.query(statement, function(err, rows, fields) { 
     if (err) throw err; 

     console.log(values + " successfully added."); 
    }); 
}; 

// Populate the wine_categories table 
var wine_categories = [ 
    'red', 'white', 'rose', 'sparkling', 'fortified' 
]; 

// Works fine when used alone 
wine_categories.forEach(function(element) { 
    insert_into('wine_categories', element); 
}); 

// Populate the countries table 
// connection.end() runs before this finishes its job 
fs.readFile(countries, 'utf8', function (err, data) { 
    if (err) { 
     throw err; 
    } else { 
     var codes = Array.prototype.map.call( 
      data.split('\n'), function(country) { 
       return country.split('\t'); 
     }); 

     codes.forEach(function(country) { 
      if(country[1].length > 25) { 
       country[1] = country[1].substring(0, 25); 
      } 
      insert_into('countries', country); 
     }); 
    } 
}); 

connection.end(); 

Очевидно, что connection.end() должно произойти после того, как все вставки закончили, но Я не уверен, как с этим справиться. Я не хочу, чтобы это было обратным вызовом для вызова readFile, потому что в конечном итоге у меня будет много похожих вызовов в этом файле.

Как я должен структурировать свой код, чтобы все запросы выполнялись, и connection.end() запускается, когда все готово? Ответ, вероятно, очевиден для асинхронного wiz ...

ответ

2

Использование обещаний было бы так:

pool.getConnectionAsync().then(function(connection) { 
    // Populate the wine_categories table 
    var wine_categories = [ 
     'red', 'white', 'rose', 'sparkling', 'fortified' 
    ]; 
    var wineQueries = wine_categories.map(function(wine){ 
     return insert_into(connection, "wine_categories", wine); 
    }); 

    var countryQueries = fs.readFileAsync(countries, "utf-8").then(function(data) { 
     return data.split("\n").map(function(country) { 
      country = country.split("\t")[1]; 
      if (country.length > 25) { 
       country = country.substring(0, 25); 
      } 
      return insert_into(connection, "countries", country); 
     }); 
    }); 

    Promise.all(wineQueries.concat(countryQueries)) 
     .then(function() { 
      console.log("all done"); 
     }) 
     .catch(function(e) { 
      console.log("error", e); 
     }) 
     .finally(function() { 
      connection.release(); 
     }) 
}); 

Предпосылкой код выше

var Promise = require("bluebird"); 
var fs = Promise.promisifyAll(require("fs")); 
Promise.promisifyAll(require("mysql/lib/Connection").prototype); 
var pool = Promise.promisifyAll(require("mysql").createPool({ 
    "user": "...", 
    "password": "...", 
    "database": "...", 
    "host": "localhost", 
    "port": 3306, 
    "debug": false 
})); 

function insert_into(connection, table, values) { 
    if(values instanceof Array) { 
     values = values.map(connection.escape, connection).join(', '); 
    } else { 
     values = connection.escape(values); 
    } 
    return connection 
     .queryAsync('INSERT INTO ' + table + ' VALUES (NULL, ' + values + ')') 
     .then(function() { 
      console.log(values + " successfully added."); 
     }); 
} 
+0

Спасибо!При незначительной модификации формата 'country' это сработало. Пара задает вопросы: 1. Если у меня больше запросов на вставку, которые я хочу использовать, могу ли я просто добавлять '.concat (queryName)' в 'Promise.all'? 2. Если дополнительные таблицы имеют внешние ключи для заполняемых таблиц, мне нужно создать новый пул, который будет запущен после этого? – EmptyArsenal

+1

@EmptyArsenal 'Promise.all' берет ряд обещаний и возвращает обещание, которое будет выполнено, когда выполняются все обещания в исходном массиве. Чтобы выполнить запрос, получите соединение из пула, затем используйте соединение для запуска запросов и, наконец, отпустите соединение. Например: http://pastebin.com/JhyvFhAS – Esailija

+0

@EmptyArsenal вы можете сделать вспомогательную функцию, подобную этой, чтобы сделать ее еще проще. Http://pastebin.com/5twg6YE1 – Esailija

1

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

async.each(codes, function(country, callback) { 
    if (country[1].length > 25) { 
    country[1] = country[1].substring(0, 25); 
    } 
    insert_into('countries', country, callback); // !! read below 
}, function(err) { 
    // TODO: handle any errors 
    ... 
    // Here, all countries are inserted. 
    connection.end(); 
}); 

Однако, это означает, что insert_into следует также принимать обратный вызов (используя общий протокол Node function(err, result)), который будет вызываться, когда запись была вставлена. В приведенном выше коде я использую обратный вызов, предоставленный async, что означает, что как только ваш insert_into будет выполнен, он вызовет сигнализацию обратного вызова async, чтобы эта итерация each была выполнена.

EDIT: вы можете переписать insert_into так это выглядит следующим образом:

function insert_into(table, values, callback) { 
    ... 
    connection.query(..., function(err) { 
    callback(err); 
    }); 
} 

Поскольку вам не нужно фактический результат от connection.query, вы только должны пройти err (вместо того, чтобы бросать его).

Совет: если вы используете node-mysql, вы можете взглянуть на документы о том, как это может помочь вам с escaping.

+0

Спасибо за руководством. Я добавил функцию «insert_into» в код. Я вижу, что ваши предложения выглядят гораздо более узкоподобными, и я все еще получаю от них зависания. Итак, в этом случае я мог бы просто записать успех или что-то в качестве обратного вызова, и этого было бы достаточно? – EmptyArsenal

+0

@EmptyArsenal см. Мое редактирование для предложения о том, как переписать 'insert_into', чтобы он вписывался в рабочий процесс' async' – robertklep