Node.js/Express项目中Promise .then语句未按顺序执行问题求助
问题原因及修复方案
核心错误原因
- 你对Promise链式调用的用法理解有误:
.then()方法要求传入回调函数作为参数,只有当前序Promise执行完成后,才会调用这个回调函数。 - 你现有代码直接把
db.checkEmail(client)、traffic.hashPassword(creds)这类异步方法的执行结果(Promise实例)传给了.then(),这些方法会在代码解析到此处时立刻并行执行,完全不会等待前序步骤完成,所以执行顺序完全乱了。 - 额外逻辑错误:你的
checkEmail方法逻辑写反了:当前只要SQL执行不报错,哪怕邮箱不存在(results.rows[0]为undefined)也会走resolve,你写的打印“邮箱不存在”的分支实际是SQL执行报错才会触发,和你的预期完全不符。
为什么你的日志顺序异常
你现有代码中checkEmail、hashPassword、hashUsername、createUser四个异步任务几乎同时启动:
- 哈希是本地计算,速度最快,所以先打印
PASSWORD HASHED、NAME HASHED createUser虽然也是数据库操作,但此时newHash、newName还没被赋值,你传了空值给数据库,插入操作很快就报错,所以打印UNABLE TO CREATE NEW USERcheckEmail需要远程查询Postgres,网络IO耗时最长,所以最后才打印邮箱校验的日志
修复方案
第一步:修正checkEmail逻辑
把queries.js里的checkEmail改成符合你预期的逻辑:
//queries.js const checkEmail = function(mail) { return new Promise(function(resolve, reject) { pool.query('SELECT * FROM clients WHERE email = $1', [mail], function(error, results) { if (error) { // SQL执行错误才reject reject(new Error('Query error when checking email: ' + error.message)); } else { if (results.rows.length > 0) { // 邮箱已存在,resolve返回用户信息 resolve(results.rows[0]); } else { // 邮箱不存在,reject提示 reject(new Error('User email not exist')); } } }) }); }
第二步:修正Promise链式调用
有两种修正方式,推荐用更易读的async/await写法:
方式1:修正原有Promise链写法
给每个.then传入回调函数,保证前序执行完再调用下一个异步方法:
// server.js 路由部分修改 app.post('/_join_up', 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_pword; var newToken = shortid.generate(); var firstname = req.body.client_alias; db.sanitation(client, creds, firstname) .then(function(direction) { console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION'); // 这里返回checkEmail的Promise,下一个then等它完成 return db.checkEmail(client); }) .then(function(foundUser) { console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser); // 邮箱已存在,直接抛出错误中断后续流程 throw new Error('Email already registered'); }, function(error) { console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...'); // 邮箱不存在,返回密码哈希的Promise return traffic.hashPassword(creds); }) .then(function(hashedPassword) { console.log('PASSWORD HASHED'); newHash = hashedPassword; // 返回用户名哈希的Promise return traffic.hashUsername(firstname); }, function(error) { console.log('UNABLE TO HASH PASSWORD...' + error); throw error; }) .then(function(hashedName) { console.log('NAME HASHED'); newName = hashedName; // 返回创建用户的Promise return db.createUser(client, newName, newHash, newToken); }, function(error) { console.log('UNABLE TO HASH NAME...' + error); throw error; }) .then(function(data) { console.log('REGISTERED A NEW CLIENT JOIN...!!!'); res.redirect('/landing'); }, function(error) { console.log('UNABLE TO CREATE NEW USER...' + error); throw error; }) .catch(function(error) { console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE USER-SUPPLIED INFORMATION...' + error); res.redirect('/'); }); });
方式2:更简洁的async/await写法(推荐)
// server.js 路由部分修改 app.post('/_join_up', async function(req, res) { if (!req.body) { console.log('ERROR: req.body has NOT been returned...'); return res.sendStatus(400) } try { const client = req.body.client_email; const creds = req.body.client_pword; const newToken = shortid.generate(); const firstname = req.body.client_alias; // 按顺序执行异步步骤 await db.sanitation(client, creds, firstname); console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION'); try { await db.checkEmail(client); // 没有抛错说明邮箱存在 console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!'); throw new Error('Email already registered'); } catch(e) { console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...'); } const newHash = await traffic.hashPassword(creds); console.log('PASSWORD HASHED'); const newName = await traffic.hashUsername(firstname); console.log('NAME HASHED'); await db.createUser(client, newName, newHash, newToken); console.log('REGISTERED A NEW CLIENT JOIN...!!!'); res.redirect('/landing'); } catch(error) { console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE USER-SUPPLIED INFORMATION...' + error); res.redirect('/'); } });
内容的提问来源于stack exchange,提问作者Pangit
相关产品推荐
相关产品推荐

