滥用 await as return 语句的缺点?
Downside to abusing await as return statement?
我在 Javascript 中偶然发现了一种有趣但相当黑的方法来编写带有 promises 的线性代码,如下所示:
const return_by_death = new Promise((resolve) => {});
和
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(async (err) => {
res.sendStatus(500);
await return_by_death;
});
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
从我的实验来看,它似乎以这样的方式工作:如果来自 generate_hash 的承诺被拒绝,它将进入我的错误处理程序而不移动到 new_account 行。我的问题如下:
这是否通过生成无限期挂起的承诺或执行线程来浪费内存?
expressjs 是否保留传递给 app.get(path, func) 的函数,它会跟踪可能永远无法解决的注册承诺?
有更好的方法吗?
编辑:基于来自@CertainPerformance 的answer/information,我提出了以下解决方案
class PromiseRunError {
constructor(obj) {
this.obj = obj;
}
}
Promise.prototype.run = function() {
return this.catch((err) => {return new PromiseRunError(err)}).then((data) => {
if(data instanceof PromiseRunError) {
return [undefined, data.obj];
}
return [data, undefined];
});
};
和
const [hash_data, hash_gen_err] = await generate_hash(pass).run();
if(hash_gen_err) {
res.sendStatus(500);
return;
}
是的,在这里使用永久未解决的 Promise 是个问题。
- 如果
signup
被多次调用,并且产生了很多挂起的Promise,随着时间的推移会使用越来越多的内存;每次调用 signup
都会产生一个新的闭包,其中分配了 user
、pass
、req
和 res
变量,直到函数结束。 (但如果有错误,它永远不会结束。)
- 从可读性的角度来看,它非常令人困惑。
如果你想让事物尽可能平坦,而不是将所有事物包围在 try
/catch
中,假设 generate_hash
return 是一个 Promise,如果它解析,将解析为真实的东西,只是不要 return .catch
处理程序中的任何内容,然后在继续之前检查 hash_data
是否真实:
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(() => {});
if (!hash_data) {
res.sendStatus(500);
return;
}
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
我更喜欢 try
/catch
,不过:
const signup = async (req, res) => {
try {
const { user, pass } = req.body;
if (!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass)
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
} catch (e) {
res.sendStatus(500);
return;
}
};
我在 Javascript 中偶然发现了一种有趣但相当黑的方法来编写带有 promises 的线性代码,如下所示:
const return_by_death = new Promise((resolve) => {});
和
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(async (err) => {
res.sendStatus(500);
await return_by_death;
});
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
从我的实验来看,它似乎以这样的方式工作:如果来自 generate_hash 的承诺被拒绝,它将进入我的错误处理程序而不移动到 new_account 行。我的问题如下:
这是否通过生成无限期挂起的承诺或执行线程来浪费内存?
expressjs 是否保留传递给 app.get(path, func) 的函数,它会跟踪可能永远无法解决的注册承诺?
有更好的方法吗?
编辑:基于来自@CertainPerformance 的answer/information,我提出了以下解决方案
class PromiseRunError {
constructor(obj) {
this.obj = obj;
}
}
Promise.prototype.run = function() {
return this.catch((err) => {return new PromiseRunError(err)}).then((data) => {
if(data instanceof PromiseRunError) {
return [undefined, data.obj];
}
return [data, undefined];
});
};
和
const [hash_data, hash_gen_err] = await generate_hash(pass).run();
if(hash_gen_err) {
res.sendStatus(500);
return;
}
是的,在这里使用永久未解决的 Promise 是个问题。
- 如果
signup
被多次调用,并且产生了很多挂起的Promise,随着时间的推移会使用越来越多的内存;每次调用signup
都会产生一个新的闭包,其中分配了user
、pass
、req
和res
变量,直到函数结束。 (但如果有错误,它永远不会结束。) - 从可读性的角度来看,它非常令人困惑。
如果你想让事物尽可能平坦,而不是将所有事物包围在 try
/catch
中,假设 generate_hash
return 是一个 Promise,如果它解析,将解析为真实的东西,只是不要 return .catch
处理程序中的任何内容,然后在继续之前检查 hash_data
是否真实:
const signup = async (req, res) => {
const user = req.body.username;
const pass = req.body.password;
if(!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass).catch(() => {});
if (!hash_data) {
res.sendStatus(500);
return;
}
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
};
我更喜欢 try
/catch
,不过:
const signup = async (req, res) => {
try {
const { user, pass } = req.body;
if (!user || !pass) {
res.sendStatus(400);
return;
}
const hash_data = await generate_hash(pass)
const new_account = new models.account.model({
username: user,
salt: hash_data.salt,
hash: hash_data.hash
});
// ...
} catch (e) {
res.sendStatus(500);
return;
}
};