'undefined' 以某种方式破坏了承诺树

'undefined' somehow breaking promise tree

我是 运行 一个 Node.js/Express 应用程序。在这段代码中,我有一个函数接受来自 'form' 的数据来注册 'new user'。此函数获取输入的用户信息并执行一些任务,例如检查非法字符、检查以确定输入的电子邮件是否已存在于数据库中、'hashes' 输入的名称和密码,最后写入 ( PostGres) 数据库 'new' 用户信息。所有这些代码都被格式化为 'promise tree' 因此每个任务都是按顺序完成的,一个接一个。代码如下:

//server.js

const db = require('./routes/queries');  
const traffic = require('./routes/traffic');

...

app.post('/_register', function(req, res) {

 if (!req.body) {
 console.log('ERROR: req.body has NOT been returned...');
 return res.sendStatus(400)
 }

 var newHash, newName;
 var newToken = shortid.generate();
 var client = req.body.user_email;
 var creds = req.body.user_password;
 var firstname = req.body.user_name;

db.sanitation(client, creds, firstname).then(function (direction) {
console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
return db.checkEmail(client);  //<==call database query here to check for existing email
}).then(function (founduser) {

   if (typeof foundUser != "undefined") {
   console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);

    if (founduser.status === "active") {res.redirect('/client_login'); }  
    return Promise.reject("Email EXTANT");  //break out of promise chain...to prevent additional code processing below...

   } else {
   console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...UNDEFINED!!!');  //appears in log  
   return traffic.hashPassword(creds);  //hash password and continue processing code below...          
   }  //'foundUser' is 'undefined'...OR NOT...

}).then(function (hashedPassword) {  
   console.log('PASSWORD HASHED');  //does NOT appear in logs
   newHash = hashedPassword;
   return traffic.hashUsername(firstname);
}).then(function (hashedName) {
   console.log('NAME HASHED');  //does NOT appear in logs
   newName = hashedName;
   return db.createUser(newName, client, newHash, newToken); 
}).then(function (data) {
    console.log('REGISTERED A NEW CLIENT JOIN...!!!');
}, 
function(error) {
    console.log('USER REGISTRATION FAILURE...');  //<==THIS MESSAGE SHOWS IN 'LOGS'...WHY???
}
).then(function () {
res.redirect('/landing');  //this page re-direction DOES occur...
}).catch(function (err) {
console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING...' + error);
res.redirect('/');  
});

});  //POST 'register' is used to register NEW users...

这是我的问题。当执行此代码并且用户电子邮件不在数据库中时,在我的日志中我看到消息“用户电子邮件当前不在数据库中......因此它没问题......未定义!!!” ...这是意料之中的,因为电子邮件不在数据库中。从这一点开始,代码应该继续处理,首先是 'hashing' 用户密码,然后继续向下 'promise tree'.

实际上发生的是用户密码和名称的 'hashing' 似乎没有发生...因为我没有看到表明它们已执行的日志消息。相反,我在日志中看到以下消息“USER REGISTRATION FAILURE...”,它表示 'failure'(拒绝)代码写入数据库。

我的问题是为什么我检查来自“checkEmail”函数的 'undefined' 响应的部分似乎没有执行我在其中的代码('return traffic.hashPassword(creds);' 函数)然后随后抛出'reject'后面的代码在'return db.createUser'.

这对我来说完全没有意义。似乎检查数据库中现存用户电子邮件的 'undefined' 响应阻止了代码其余部分的执行,并且莫名其妙地抛出 'rejection' 数据库写入。

这让我很难受。我花了大约一个星期的时间,但我似乎离解决这个问题还差得很远。如果我处理来自 'checkEmail' 调用的 'undefined' return 的代码在某种程度上不正确,有人可以演示执行此操作的正确方法吗?非常感谢任何建议。

我在上面的代码中做了注释,以说明我的日志中显示的内容和未显示的内容

更新:

根据收到的反馈,我使用两种不同的方法重写了上面的代码。这是第一个:

app.post('/register', function(req, res) {

 if (!req.body) {
 console.log('ERROR: req.body has NOT been returned...');
 return res.sendStatus(400)
 }

 var newHash, newName;
 var client = req.body.client_email;
 var creds = req.body.client_password;
 var newToken = shortid.generate();
 var firstname = req.body.client_name; 

  try {
    const users = db.checkEmail(client);  
  
    users.then(function(result) {
    console.log('FINAL RESULT ROWS ARE: ' + result.rows)
    
     if (typeof result.rows != "undefined") {
     console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!');
     
      if (result.status === "active") {
        console.log("Email EXTANT");
        return res.redirect("/client_login");
      }  //"active"      
     
     } else {
     console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...');
     return traffic.hashPassword(creds);         
     }  //'result.rows' is 'undefined'...OR NOT...      
    
    })  
    .then(function(result) {
    console.log('PASSWORD HASHED');     
    console.log(result); 
    newHash = result;   
    return traffic.hashUsername(firstname);     
    })    
    .then(function(result) {
    console.log('NAME HASHED');
    newName = result;
    return db.createUser(newName, client, newHash, newToken);       
    })  
    .then(function(result) {
    console.log('REGISTERED A NEW CLIENT JOIN...!!!');
    })    
    .then(function(result) {
    res.redirect('/landing');  //route to 'landing' page...
    });   
  
  } catch(err) {
    // handle errors
    console.log('ERROR IN TRY/CATCH IS: ' + err);     
  }

});  //POST 'register' is used to register NEW clients...

此代码是有效的,但它总是报告 'email' 不在数据库中......即使实际上它是。这是输出日志:

FINAL RESULT ROWS ARE: undefined
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...
PASSWORD HASHED
b$vW3.YkPyoB9MG5k9qiGreOQC05rWsEIO6i.NkYg6oFqJ8byNjp.iu
NAME HASHED
REGISTERED A NEW CLIENT JOIN...!!!

这是第二个代码块,在函数中使用 'async/await':

app.post('/register', async function(req, res) {

 if (!req.body) {
 console.log('ERROR: req.body has NOT been returned...');
 return res.sendStatus(400)
 }

 var newHash, newName;
 var client = req.body.client_email;
 var creds = req.body.client_password;
 var newToken = shortid.generate();
 var firstname = req.body.client_name;

  try {
  //const direction = await db.sanitation(client, creds, firstname);
  const founduser = await db.checkEmail(client);
  console.log('founduser ROWS ARE: ' + founduser.rows)    
  
   if (typeof foundUser != "undefined") {
   console.log("HEY THERE IS ALREADY A USER WITH THAT EMAIL!", foundUser);

    if (founduser.status === "active") {
      console.log("Email EXTANT");
      return res.redirect("/client_login");
    }
     
   }  //NOT "undefined"

   console.log("USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...!!!");    
  
  
  } catch (err) {
  console.log("THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE TRY STATEMENT..." + err);
  return res.redirect("/");
  }

});  //POST 'register' is used to register NEW clients...

这段代码也可以正常工作,但是与第一段代码一样,它总是报告 'email' 不在数据库中……即使实际上是这样。这是输出日志:

USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...!!!

根据这些结果,我相信任何一个代码块都可能起作用......并且所有执行报告电子邮件为 'undefined' 的原因(即使它已经存在于数据库中)是因为的“checkEmail”功能。我可能把它错误地写成了正确的 return 和 'async' 结果。这是代码:

const Pool = require('pg').Pool;

const pool = new Pool({
  user: 'postgres',
  host: '127.0.0.1',
  database: 'myDB',
  password: 'password',
})

const checkEmail = async function(mail) {

 return new Promise(function(resolve, reject) {

  pool.query('SELECT * FROM clients WHERE email = ', [mail], function(error, results) {
   if (error) {
   reject(new Error('Error processing a database check for email!'));     
   } else {
   resolve(results.rows);
   }
   console.log('checkEmail mail: ' + mail); 
   console.log('checkEmail results.rows: ' + results.rows);
  })  //pool.query

 });  //new promise 

}

是否有人能够证实我的怀疑,上面的“try/catch”代码块都写得正确......而且调用的问题总是returning“undefined”在“checkEmail”函数中?而且,如果是这样的话……也许建议我需要如何更正“checkEmail”功能,以便在必要时正确地找到数据库中的现有电子邮件。我不太熟悉 'async' 函数的用法,也从未尝试过在查询数据库的承诺中使用它们。提前感谢您的回复。

UPDATE/SOLUTION:

当我第一次编写“checkEmail”promise 函数时,我假设如果在数据库中发现了匹配的电子邮件,它将 'resolve'...如果没有,则 'reject'。我遇到的是该函数始终 'resolves',即使电子邮件不在数据库中也是如此。因此,我发现 'object.keys' 方法的用法对于检查实际上是否有一些数据 return 从函数中编辑很有用。使用它我可以编写现在看起来可以正常运行的代码。这是我当前的“checkEmail”功能:

//queries.js

const checkEmail = async function(mail) {

 return new Promise(function(resolve, reject) {

  pool.query('SELECT * FROM clients WHERE email = ', [mail], function(error, results) {
   if (error) {
   reject(new Error('Error processing a database check for email!'));     
   } else {
   resolve(results.rows);
   }
   console.log('checkEmail mail: ' + mail); 
   console.log('checkEmail results.rows: ' + results.rows);    
  })  //pool.query

 });  //new promise 

}

module.exports = {
...
checkEmail,
...
}

还有我的承诺树:

//server.js

app.post('/register', function(req, res) {  

 if (!req.body) {
 console.log('ERROR: req.body has NOT been returned...');
 return res.sendStatus(400)
 }

 var client = req.body.client_email;
 var creds = req.body.client_password;
 var newToken = shortid.generate();
 var firstname = req.body.client_name; 

  db.sanitation(client, creds, firstname)
  .then(function (direction) {
  console.log('Result direction Object.keys from SANITATION: ', Object.keys(direction).length);     
  console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
  return db.checkEmail(client); // <==call database query here to check for existing email for existing email
  })
  .then(function (founduser) {

  console.log('foundUser matching email in database: ', founduser);     
  console.log('foundUser Object.keys matching email in database: ', Object.keys(founduser).length); 
    
   if (Object.keys(founduser).length > 0) { 
   console.log('EMAIL IS EXTANT IN DATABASE ALREADY!'); 
    if (founduser.length) {console.log('foundUser LENGTH matching email in database: ', founduser.length);}
 
     if (founduser[0].status === 'active') {
     console.log('USER-SUPPLIED EMAIL EQUALS THAT OF AN ACTIVE USER');     
     throw new Error('active');  //break out of promise chain...to prevent additional code processing below...
     } else {
     console.log('USER-SUPPLIED EMAIL APPEARS IN THE DATABASE');       
     throw new Error('Email EXTANT');  //break out of promise chain...to prevent additional code processing below...
     }  //founduser[0].status    
 
   }  //founduser.length EXCEEDS "0"        
   if (Object.keys(founduser).length === 0) { 
   console.log('EMAIL IS NOT PRESENT IN THE DATABASE!'); 
   return traffic.hashPassword(creds); // hash password and continue processing code below...
   }  //founduser.length EQUALS "0" 
  })
  .then(function (hashedPassword) {
    console.log('PASSWORD HASHED');
    return traffic.hashUsername(firstname)
    .then(function (hashedName) { // nested to keep hashedPassword within scope
        console.log('NAME HASHED');
        return db.createUser(hashedName, client, hashedPassword, newToken)
        .catch(function (error) { // nested in order to catch only an error arising from db.createUser(), (not necessary other than to log out an error message).
            console.log('USER REGISTRATION FAILURE...'); // this message will appear only if db.createUser() fails
            throw error; // RETHROW error in order to jump to the terminal catch (and hit the `default` case).
          });
      });
  })
  .then(function (data) {
    console.log('REGISTERED A NEW CLIENT JOIN...!!!');
    res.redirect('/landing'); // success
  })
  .catch(function (err) {

    switch(err.message) {
        case 'active':
            res.redirect('/client_login');
        break;
        case 'Email EXTANT':
            res.redirect('/client_login');      
        break;      
        default: // all unexpected errors
            console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING... ' + err.message);
            res.redirect('/');
    }
  });

});  //POST 'register' is used to register NEW clients...

我要感谢那些对此做出回应的人 post。我非常感谢他们的时间和建议,使我能够了解现在显然是功能代码的这一点。此外,这些回复非常有启发性,我从收到的帮助中学到了一些新技巧。

我认为问题是return Promise.reject("Email EXTANT");。如果你想中断执行,你可以使用 return res 代替。

使用 asyn/await 方法尝试下面的示例。

编辑:添加检查电子邮件更新

//server.js

const db = require("./routes/queries");
const traffic = require("./routes/traffic");

app.post("/_register", async function (req, res) {
  if (!req.body) {
    console.log("ERROR: req.body has NOT been returned...");
    return res.sendStatus(400);
  }

  var newToken = shortid.generate();
  var client = req.body.user_email;
  var creds = req.body.user_password;
  var firstname = req.body.user_name;

  try {
    const direction = await db.sanitation(client, creds, firstname);
    const foundusers = await db.checkEmail(client);

    if (foundusers.length) {
      console.log(
        "HEY THERE IS ALREADY A USER WITH THAT EMAIL!",
        foundusers[0]
      );

      if (foundusers[0].status === "active") {
        console.log("Email EXTANT");
        return res.redirect("/client_login");
      }
    }

    console.log(
      "USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...UNDEFINED!!!"
    );

    const hashedPassword = await traffic.hashPassword(creds);

    console.log("PASSWORD HASHED");

    const hashedName = await traffic.hashUsername(firstname);

    await db.createUser(hashedName, client, hashedPassword, newToken);
    console.log("REGISTERED A NEW CLIENT JOIN...!!!");

    return res.redirect("/landing");
  } catch (err) {
    console.log("THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING..." + err);
    return res.redirect("/");
  }
});

我更新了 checkEmail 函数。

提醒:您应该创建一个 db.js 来导出池,而不是在 checkEmail.js 文件中创建一个池。然后当你需要在其他函数中查询时,他们可以从中导入池而不是重新创建一个新池。

const Pool = require("pg").Pool;

const pool = new Pool({
  user: "postgres",
  host: "127.0.0.1",
  database: "myDB",
  password: "password",
});

export const checkEmail = async function (mail) {
  try {
    const res = await pool.query("SELECT * FROM clients WHERE email = ", [
      mail,
    ]);
    console.log(res);
    return res.rows;
  } catch (err) {
    throw err;
  }
};

我想知道您是否可能遗漏了以下控制 promise 链错误的基本原则中的一项或多项:

  • 如果捕获到一个错误并且您不希望它被标记为已处理(例如,如果您捕获一个错误只是为了记录它)那么您必须重新抛出错误(或抛出您自己的错误)以便沿着承诺链的错误路径继续前进。
  • 如果错误被捕获并且没有重新抛出,那么 promise 链将沿着它的成功路径前进。如果未明确返回值,则 undefined 将传递到下一步。
  • 自然发生或故意抛出的错误将传播到下一个符合条件的 .catch()
  • 给定链中的 .catch() 将捕获任何较早的错误,而不仅仅是前一步骤引起的错误。
  • a .catch().then(successHander, errorHandler) 的形式编写将捕获链中前面步骤的错误,但不会捕获 successHander 的错误。这可能很有用(但在这里没有用)。
  • a .catch() 通常可以通过将其嵌套在主链中使其成为“私有”(即特定于特定的异步步骤)。这避免了捕获链中早期出现的错误。
  • 在 promise 链中抛出错误比 return Promise.reject(...) 更经济。

您可以将重定向嵌入链中,但我建议在终端 .catch() 中抛出错误和分支会更清晰(例如使用 switch/case 结构)。

您最终可能会得到这样的结果(代码中有大量注释)...

//server.js
const db = require('./routes/queries');  
const traffic = require('./routes/traffic');
...
app.post('/_register', function(req, res) {
    if (!req.body) {
        console.log('ERROR: req.body has NOT been returned...');
        return res.sendStatus(400)
    }
    // var newHash, newName; // not needed
    var newToken = shortid.generate();
    var client = req.body.user_email;
    var creds = req.body.user_password;
    var firstname = req.body.user_name;
    db.sanitation(client, creds, firstname)
    .then(function (direction) {
        console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
        return db.checkEmail(client); // <==call database query here to check for existing email
    })
    .then(function (founduser) {
        if (typeof foundUser != "undefined") { // not a particularly good test, maybe if(foundUser) {...} would be better? 
            console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);
            if (founduser.status === 'active') {
                throw new Error('active'); // break out of promise chain...to prevent additional code processing below...
            } else {
                throw new Error('Email EXTANT'); // break out of promise chain...to prevent additional code processing below...
            }
        } else {
            console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...UNDEFINED!!!');  // appears in log
            return traffic.hashPassword(creds); // hash password and continue processing code below...
        }
    })
    .then(function (hashedPassword) {
        console.log('PASSWORD HASHED');
        return traffic.hashUsername(firstname)
        .then(function (hashedName) { // nested to keep hashedPassword within scope
            console.log('NAME HASHED');
            return db.createUser(hashedName, client, hashedPassword, newToken)
            .catch(function (error) { // nested in order to catch only an error arising from db.createUser(), (not necessary other than to log out an error message).
                console.log('USER REGISTRATION FAILURE...'); // this message will appear only if db.createUser() fails
                throw error; // RETHROW error in order to jump to the terminal catch (and hit the `default` case).
            });
        });
    })
    .then(function (data) {
        console.log('REGISTERED A NEW CLIENT JOIN...!!!');
        res.redirect('/landing'); // success
    })
    .catch(function (err) {
        // Suggest you perform all error case redirects here, depending on which error occurred.
        // May not be 100% correct but you get the idea.
        switch(err.message) {
            case 'active':
                res.redirect('/client_login');
            break;
            case 'Email EXTANT':
            default: // all unexpected errors
                console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING... ' + err.message);
                res.redirect('/');
        }
    });
}); // POST 'register' is used to register NEW users...