我正在使用Promise处理一些继承的代码,但遇到一个重大问题。我有两个函数,都返回一个promise(或在findOne情况下为一系列promise),其中findAll调用findOne来遍历许多记录,以使用Sequelize从其他表中的那些记录中查询数据。我已尝试对代码进行布局,以便在返回父函数findAll之前,解析或拒绝传递给findOne的所有promise。
给定findAll中的许多匹配记录,我将每条记录传递给findOne,当findOne解析它时,它将调用另一个函数ConstructJson,该函数返回一个值而不是一个Promise。我看到的问题是,一旦所有记录都通过findOne并返回到findAll,则代码实际上多次返回到findOne函数的resolve(constructJson(_map,scrub))行。在测试用例中,应该给我返回四个不同的记录ID(传递给findOne以进行查询),findOne返回的是重复的ID或乱序的ID。我很少会以传递给findOne的顺序返回相同的四个顺序ID,尽管基于函数的流程看来应该是这种情况。为了确保findOne返回的承诺已完全完成,传递给它的关联ID(查询对象)已按顺序返回,因此我缺少异步性。我没看到什么?
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);
});
});
}