#javascript #node.js #excel #bluebird #pg-promise
#javascript #node.js #преуспеть #bluebird #pg-обещание
Вопрос:
Итак, я недавно углубился в попытки понять обещания и стоящую за ними цель из-за асинхронного поведения javascripts. Хотя я «думаю», что понимаю, я все еще пытаюсь понять, как что-то пообещать, чтобы вернуть будущее значение, а затем выполнить новый блок кода, чтобы сделать что-то еще. Я использую два основных модуля узла:
- pg-обещание
- exceljs
То, что я хотел бы сделать, это прочитать файл, затем после полного чтения выполнить итерацию каждого рабочего листа, выполняя команды DB. Затем, как только все рабочие листы будут обработаны, вернитесь и удалите исходный файл, который я прочитал. Вот код, который у меня есть. У меня это работает до такой степени, что все записывается в базу данных просто отлично, даже при наличии нескольких листов. Что у меня не работает, так это настройка его на определение того, когда все рабочие листы были полностью обработаны, а затем на удаление файла
workbook.csv.readFile(fileName)
.then(function () {
// this array I was going to use to somehow populate a true/false array.
// Then when done with each sheet, push a true into the array.
// When all elements were true could signify all the processing is done...
// but have no idea how to utilize this!
// So left it in to take up space because wtf...
var arrWorksheetComplete = [];
workbook.eachSheet(function (worksheet) {
console.log(worksheet.name);
db.tx(function (t) {
var insertStatements = [];
for (var i = 2; i <= worksheet._rows.length; i ) {
// here we create a new array from the worksheet, as we need a 0 index based array.
// the worksheet values actually begins at element 1. We will splice to dump the undefined element at index 0.
// This will allow the batch promises to work correctly... otherwise everything will be offset by 1
var arrValues = Array.from(worksheet.getRow(i).values);
arrValues.splice(0, 1);
// these queries are upsert. Inserts will occur first, however if they error on the constraint, an update will occur instead.
insertStatements.push(t.one('insert into rq_data'
'(col1, col2, col3) '
'values($1, $2, $3) '
'ON CONFLICT ON CONSTRAINT key_constraint DO UPDATE SET '
'(prodname) = '
'($3) RETURNING autokey',
arrValues));
}
return t.batch(insertStatements);
})
.then(function (data) {
console.log('Success:', 'Inserted/Updated ' data.length ' records');
})
.catch(function (error) {
console.log('ERROR:', error.message || error);
});
});
});
Я хотел бы иметь возможность сказать
.then(function(){
// everything processed!
removeFile(fileName)
// this probably also wouldn't work as by now fileName is out of context?
});
Но я очень смущен, когда у меня есть обещание внутри обещания.. У меня есть вызов db.tx, который по сути является обещанием, вложенным в функцию .eachSheet .
Пожалуйста, помогите тупому программисту понять! Часами бился головой об стену над этим. 🙂
Комментарии:
1. Вам нужно пообещать
workbook.eachSheet
и связать его. И вы должны выйтиdb.tx
за пределыeachSheet
call, потому что вам нужна только одна транзакция базы данных.2. Пообещайте ВСЕ ваши асинхронные операции, а затем, когда вы вложите их, верните обещание связать их. НЕ смешивайте регулярные асинхронные обратные вызовы с обещаниями.
3. @vitaly-я осматриваюсь, и я изо всех сил пытаюсь понять, как «обещать» функцию eachsheet. Есть ли какой-нибудь пример, на который вы тоже могли бы мне указать?
4. Вы можете решить эту проблему, используя promises в сочетании с генератором ES6 — какие-либо изменения окружающей среды препятствуют вам в этом?
Ответ №1:
Если я правильно понимаю, вы пытаетесь связать свои обещания цепочкой.
Я предлагаю вам прочитать эту замечательную статью об антишаблоне Promises (см. Раздел «Сбой в коллекции»).
Если вам нужно выполнять обещания последовательно, в этой статье предлагается использовать reduce .
Я перепишу ваш фрагмент в:
workbook.csv.readFile(fileName).then(function () {
processWorksheets().then(function() {
// all worksheets processed!
});
});
function processWorksheets() {
var worksheets = [];
// first, build an array of worksheet
workbook.eachSheet(function (worksheet) {
worksheets.push(worksheet);
});
// then chain promises using Array.reduce
return worksheets.reduce(function(promise, item) {
// promise is the the value previously returned in the last invocation of the callback.
// item is a worksheet
// when the previous promise will be resolved, call saveWorksheet on the next worksheet
return promise.then(function(result) {
return saveWorksheet(item, result);
});
}, Promise.resolve()); // start chain with a 'fake' promise
}
// this method returns a promise
function saveWorksheet(worksheet, result) {
return db.tx(function (t) {
var insertStatements = [];
for (var i = 2; i <= worksheet._rows.length; i ) {
// here we create a new array from the worksheet, as we need a 0 index based array.
// the worksheet values actually begins at element 1. We will splice to dump the undefined element at index 0.
// This will allow the batch promises to work correctly... otherwise everything will be offset by 1
var arrValues = Array.from(worksheet.getRow(i).values);
arrValues.splice(0, 1);
// these queries are upsert. Inserts will occur first, however if they error on the constraint, an update will occur instead.
insertStatements.push(t.one('insert into rq_data'
'(col1, col2, col3) '
'values($1, $2, $3) '
'ON CONFLICT ON CONSTRAINT key_constraint DO UPDATE SET '
'(prodname) = '
'($3) RETURNING autokey',
arrValues));
}
return t.batch(insertStatements);
})
// this two below can be removed...
.then(function (data) {
return new Promise((resolve, reject) => {
console.log('Success:', 'Inserted/Updated ' data.length ' records');
resolve();
});
})
.catch(function (error) {
return new Promise((resolve, reject) => {
console.log('ERROR:', error.message || error);
reject();
});
});
}
Не забудьте включить модуль promise:
var Promise = require('promise');
Я не тестировал свой код, возможно, он содержит некоторые ошибки с опечатками.
Комментарии:
1. Одна из ключевых идей работы с таблицами данных заключается в том, чтобы избежать перегрузки Node.js память. Не следует складывать все листы в память, их следует читать и обрабатывать один за другим.
2. @vitaly-t вы правы, мой код посвящен связыванию обещаний… Его можно редактировать, одним из подходов может быть перебор имен файлов рабочих листов и загрузка соответствующего внутри обратного вызова reduce.
3. требования изменились, и в итоге пришлось работать только с одним рабочим листом … так что можно было бы удалить вызов цикла. Я все же адаптировал часть этой логики! Спасибо!