(Node.js и Promises) Не удается найти проблему с разрешением обещания - PullRequest
0 голосов
/ 07 февраля 2019

Я работаю через некоторый унаследованный код, используя обещания и имея серьезную проблему.У меня есть две функции, обе возвращающие обещание (или серию обещаний в случае 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);
    });
  });
}
...