【问题标题】:'undefined' somehow breaking promise tree“未定义”以某种方式破坏了承诺树
【发布时间】:2021-11-24 14:11:45
【问题描述】:

我正在运行Node.js/Express 应用程序。在这段代码中,我有一个函数,它接受来自“表单”的数据来注册一个“新用户”。此函数获取输入的用户信息并执行一些任务,例如检查非法字符,检查确定输入的电子邮件是否已经存在于数据库中,对输入的名称和密码进行“哈希”处理,最后写入 (PostGres)数据库“新”用户信息。所有这些代码都被格式化为一个“承诺树”,因此每个任务都是按顺序完成的,一个接一个。代码如下:

//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...

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

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

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

这对我来说完全没有意义。似乎来自检查数据库中现有用户电子邮件的“未定义”响应阻止了代码其余部分的执行,并且莫名其妙地引发了数据库写入的“拒绝”。

这可要了我的命。我花了大约一周的时间,我似乎离解决这个问题还差得远。如果我处理来自“checkEmail”调用的“未定义”返回的代码在某种程度上不正确,有人可以演示执行此操作的正确方法吗?非常感谢任何建议。

我在上面的代码中添加了注释符号来说明日志中显示的内容和未显示的内容

更新:

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

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...

此代码是有效的,但它总是报告“电子邮件”不在数据库中......即使事实上它是。这是输出的日志:

FINAL RESULT ROWS ARE: undefined
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...
PASSWORD HASHED
$2b$10$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...

此代码也可以正常工作,但是与第一段代码一样,它总是报告“电子邮件”不在数据库中......即使事实上它是。这是输出的日志:

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

基于这些结果,我相信任何一个代码块都可能正常运行......并且所有执行的原因都将电子邮件报告为“未定义”(即使它已经存在于数据库中)是因为“checkEmail “ 功能。我可能写错了正确返回“异步”结果。这是代码:

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 = $1', [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”代码块都编写正确......并且调用总是返回“undefined”的问题在于“checkEmail”函数?而且,如果是这种情况......也许建议我需要如何更正“checkEmail”功能,以便在必要时正确找到数据库中的现有电子邮件。我对“异步”函数的使用并不十分熟悉,也从未尝试在查询数据库的承诺中使用它们。提前感谢您的任何回复。

更新/解决方案:

当我第一次编写“checkEmail”承诺函数时,我认为如果在数据库中发现了匹配的电子邮件,它会“解决”……如果不是,则“拒绝”。我遇到的是该功能总是“解决”,即使电子邮件不在数据库中。因此,我发现使用“object.keys”方法有助于检查是否确实从函数返回了一些数据。使用它,我可以编写现在似乎可以正常运行的代码。这是我当前的“checkEmail”功能:

//queries.js

const checkEmail = async function(mail) {

 return new Promise(function(resolve, reject) {

  pool.query('SELECT * FROM clients WHERE email = $1', [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...

我要感谢那些回复这篇文章的人。我非常感谢他们的时间和建议,使我能够达到现在显然是功能性代码的这一点。此外,这些回复很有启发性,而且我从收到的帮助中学到了一些新技术。

【问题讨论】:

  • 你能分享traffic 路由器文件吗?那里似乎有什么东西正在破坏,将执行流程更改为拒绝。 traffic.hashPassword(creds);在做什么,请加代码?
  • 感谢您的回复。我在上面添加了该功能。我认为这不是问题......我似乎从未遇到过该功能的任何问题,除非我严重错误。再次感谢您的意见。
  • 您是否尝试将调试器添加到文件并检查流程?这似乎是弄清楚流程如何进入的最佳方法,您可以检查每个变量
  • 您指的是 PostGres 调试器吗?几周前我曾尝试过,但由于某种原因无法使其正常工作。
  • 不,VScode 或您正在使用的任何其他 IDE 中的节点、js 调试器。在代码中添加断点并检查超出范围的地方。

标签: node.js postgresql express promise undefined


【解决方案1】:

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

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

编辑:添加 checkEmail 更新

//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 = $1", [
      mail,
    ]);
    console.log(res);
    return res.rows;
  } catch (err) {
    throw err;
  }
};

【讨论】:

  • 感谢您的回复。我将进行更改并执行以查看可能产生的影响然后报告。感谢您的宝贵时间。
  • 您好 ikhvjs,我已经尝试过您的建议(以及另一种方法)。在“更新:”指示之后,我的当前进度显示在上方。我相信你的代码是正常的,但是由于某种原因,我总是从“checkEmail”函数返回一个“未定义”,即使数据库中存在电子邮件......我怀疑这可能是由我的潜在错误引起的“checkEmail”功能...?
  • @Pangit,你用的是哪个 db 库?你能在你的问题中添加它吗? db库的使用可能有一些不正确的地方。
  • 您好 ikhvjs,我正在使用“pg”库进行数据库连接。我在上面的“checkEmail”代码上方添加了该信息。再次感谢。
  • 再次感谢您的宝贵时间和建议。我非常感谢您的帮助。我已经在上面的“更新/解决方案”中发布了一些我目前正在使用的似乎运行正常的代码。大道具。
【解决方案2】:

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

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

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

你可能会得到这样的结果(代码中有很多 cmets)...

//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...

【讨论】:

  • 感谢您对 Roamer-1888 的广泛回复,非常感谢。我相信我了解您的代码,并且我发现“抛出”和错误处理的用法非常有用。确实,我之前没有考虑过这些因素……主要是由于对 Promise 处理缺乏经验。我肯定会测试这段代码,但是我可能要到明天才能这样做。一旦我观察到结果,我会在那个时候报告。再次非常感谢您的时间和洞察力。问候。
  • 我正在取得一些进展,请参阅上面的“第二次更新”编辑。现在我主要的困惑是为什么 'console.log('foundUser.rows is: ', foundUser.rows);'导致抛出错误...显然,如果“foundUser”为“未定义”,则对其的任何命令引用都会引发错误。因此,我不确定如何使用这些信息...?
  • 听起来foundUser 是假的,但不是undefined。尝试将测试更改为if (foundUser)
  • 再次感谢您的帮助。我在上面的“更新/解决方案”中发布了一些似乎可以正常工作的当前代码。我发现您的代码帖子非常有启发性。我以前没有意识到“catch/throw”语句的正确用法和潜力以及与路由导航的关系。你的帖子很有指导意义,它可能会为其他没有充分利用有效承诺“捕获”的人提供教程。非常感谢!
  • 是的,我可以看到“async/await”方法似乎有点“清理”了一些东西......但是我决定继续使用“.then”语句,因为我没有太多使用'异步/等待'。此外,您显示的代码增加了我对其用法的理解,并展示了一些具有潜在“捕获”导航和错误报告的附加功能
猜你喜欢
  • 2023-03-26
  • 2011-12-08
  • 2015-08-11
  • 2016-02-08
  • 1970-01-01
  • 2018-03-28
  • 2016-11-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多