Я работаю через некоторый унаследованный код, используя обещания и имея серьезную проблему.У меня есть две функции, обе возвращающие обещание (или серию обещаний в случае findOne), где findAll выполняет вызовы findOne для циклического перебора ряда записей для запроса данных из этих записей в других таблицах с использованием Sequelize.Я попытался выложить код так, чтобы все обещания, переданные findOne, были разрешены или отклонены перед возвратом к родительской функции findAll.
Учитывая количество совпадающих записей в findAll, я передаю каждую запись findOne, который к моменту разрешения findOne вызывает другую функцию constructJson, которая возвращает значение, а не обещание.Проблема, которую я вижу, состоит в том, что как только все записи проходят через findOne и возвращаются в findAll, код фактически возвращается к строке разрешения (constructJson (_map, scrub)) функции findOne несколько раз.В моем тестовом сценарии использования, где мне должны быть возвращены четыре разных идентификатора записи (которые передаются в findOne для запроса), findOne возвращает дублированные или вышедшие из строя идентификаторы.Я очень редко получаю те же четыре последовательных идентификатора, возвращенные в том же порядке, в котором они были переданы для findOne, хотя, основываясь на потоке функции, кажется, что так и должно быть.В асинхронности есть часть, которую я пропускаю, чтобы гарантировать, что обещание, возвращаемое findOne, полностью выполнено, и связанные с ним идентификаторы (объект запросов) возвращаются по порядку.Что я не вижу?
const Sequelize = require('sequelize');
function findAll(dbConn, _map, queries, joins, primaryTable, primaryKey, whereClause, limit, offset, scrub) {
return new Promise((resolve, reject) => {
const sequelize = new Sequelize(dbConn.db, dbConn.username, dbConn.password, dbConn.options);
offset = isNaN(offset) ? 0 : offset;
whereClause = !!whereClause ? ` WHERE true${whereClause}` : ' WHERE true';
sequelize.query(`SELECT count(${primaryKey}) FROM ${primaryTable}${whereClause}`, {type: sequelize.QueryTypes.SELECT})
.then(result => {
totalRows = result[0]["count"];
});
whereClause = isNaN(limit) ? whereClause : whereClause + ` LIMIT ${limit} OFFSET ${offset}`;
sequelize.query(`SELECT ${primaryKey} FROM ${primaryTable}${whereClause}`, {type: sequelize.QueryTypes.SELECT})
.then(matchingRecords => {
let promiseStack = [];
matchingRecords.forEach(record => {
const cloneOfMap = JSON.parse(JSON.stringify(_map));
promiseStack.push(
findOne(dbConn, cloneOfMap, queries, joins, primaryTable, primaryKey, record.id, scrub)
.then(result => {
return result;
})
.catch(error => {
return {err: `error collecting data for id = ${record.id}\n${error}`};
})
)
});
Promise.all(promiseStack).then(bundleEntries => {
sequelize.close();
let errors = bundleEntries.filter(r => {return !!r.err}).map(r => r.err);
if (errors.length === 0) {
resolve(bundleEntries);
} else {
reject("The following queries failed:\n" + errors.join('\n'));
}
});
})
.catch(error => {
sequelize.close();
reject(error);
});
});
}
function findOne(dbConn, _map, queries, joins, primaryTable, primaryKey, id, scrub) {
return new Promise((resolve, reject) => {
let promiseStack = [];
let noMatchesReturned = true;
const sequelize = new Sequelize(dbConn.db, dbConn.username, dbConn.password, dbConn.options);
let queryString;
queries.forEach(query => {
if (!!query.sql && query.sql !== '') {
switch (dbConn.options.dialect) {
case "postgres":
queryString = query.sql + ` WHERE ${primaryTable}.${primaryKey}='${id}' LIMIT 1`;
break;
case "mssql":
queryString = query.sql + ` WHERE ${primaryTable}.${primaryKey}='${id}'`;
break;
}
}
else {
let otherTable = '';
let joinString = '';
if (query.table !== primaryTable) {
otherTable = ", " + primaryTable;
joins.filter(j => {return j.fkTable === primaryTable && j.pkTable === query.table}).forEach(j => {
joinString += ` AND ${j.fkTable}.${j.fkColumn} = ${j.pkTable}.${j.pkColumn}`;
});
}
switch (dbConn.options.dialect) {
case "postgres":
queryString = `SELECT ${query.table}.${query.column} FROM ${query.table}${otherTable} WHERE ${primaryTable}.${primaryKey}='${id}'${joinString} LIMIT 1`;
break;
case "mssql":
queryString = `SELECT TOP 1 ${query.table}.${query.column} FROM ${query.table}${otherTable} WHERE ${primaryTable}.${primaryKey}='${id}'${joinString}`;
break;
}
}
// console.log(queryString);
promiseStack.push(
sequelize.query(queryString, {type: sequelize.QueryTypes.SELECT})
.then(row => {
let newData;
if (row.length === 0) {
newData = null;
} else {
noMatchesReturned = false;
const x = row[0];
const y = Object.keys(x)[0];
newData = x[y];
}
return Object.assign(query, {data: newData});
})
.catch(error => {
return {err: `Error executing query ${error.sql}\n\t${error}`};
})
);
});
Promise.all(promiseStack).then(rows => {
// identify queries that returned an error
sequelize.close();
let errors = rows.filter(r => {return !!r.err}).map(r => r.err);
if (errors.length === 0) {
if (noMatchesReturned) {
resolve([]);
}
else {
// all queries completed without error
rows.forEach(result => {
_map.rows[result.id].data = result.data;
});
resolve(constructJson(_map, scrub));
}
}
else {
sequelize.close();
reject("The following queries failed:\n" + errors.join('\n'));
}
}, error => {
sequelize.close();
reject(error);
});
});
}