res.status().send() 在 Promise.all 中无法正常工作
res.status().send() not working correctly in Promise.all
我正在编写一个代码来检查来自 promise.all 方法中两个不同 API 调用的授权,如果其中任何一个授权失败,将抛出相应的 res.send 方法作为错误,但我的控制台上显示 error : Cannot set headers after they are sent to the client
错误,我哪里出错了?
在屏幕上,显示 res.send 语句,但与此同时,此 error : Cannot set headers after they are sent to the client
错误显示在我的控制台上。我该如何解决这个问题?
我用两种不同的方式编写代码,但每次都显示相同的错误。
第一种方式(没有 .catch):
const isSubscribed = new Promise((resolve, reject) => {
apiGet("/isSubscribed", token).then(async (Response) => {
if (!isResStatusSubscribed(Response)) return res.status(401).send({ errorMessage: "Unauthorized Request." })
})
})
const isAdmin = new Promise((resolve, reject) => {
apiGet("/isAdmin", token).then(async (response) => {
let isAdmin = response.data.Response.is_admin
if (!isAdmin) return res.status(403).send({ errorMessage: "User is not an Admin" })
})
})
Promise.all([isSubscribed, isAdmin]).then(async () => {
await insertLiveClassDB(req.body)
return res.status(200).send({ Response: "Success." })
});
第二种方式(使用 .catch):
const isSubscribed = new Promise((resolve, reject) => {
apiGet("/isSubscribed", token).then(async (Response) => {
if (!isResStatusSubscribed(Response)) reject(res.status(401).send({ errorMessage: "Unauthorized Request." }))
})
})
const isAdmin = new Promise((resolve, reject) => {
apiGet("/isAdmin", token).then(async (response) => {
let isAdmin = response.data.Response.is_admin
if (!isAdmin) reject(res.status(403).send({ errorMessage: "User is not an Admin" }))
})
})
Promise.all([isSubscribed, isAdmin])
.then(async () => {
await insertLiveClassDB(req.body)
return res.status(200).send({ Response: "Success." })
})
.catch(error => {
return error
});
我是表达js和写promise.all方法的新手,真的需要帮助。提前谢谢你。
这里有很多地方出了问题。首先,您可以对每个传入的 http 请求发送一个且仅一个响应。因此,您永远不应该并行启动多个异步操作并让每个操作都发送响应。相反,使用 Promise.all()
跟踪两个异步操作并在 Promise.all()
承诺完成时发送响应。
此外,您还有几个 promise 构造函数示例 anti-pattern,您在其中围绕已经 return 是 promise 的函数包装新的 promise。出于多种原因,这被认为是 anti-pattern。不仅是不必要的额外代码(直接return你已有的promise即可),而且在错误处理上也容易出错
以下是我的建议:
// stand-alone functions can be declared in a higher scope and
// used by multiple routes
const isSubscribed = function(token) {
return apiGet("/isSubscribed", token).then((Response) => {
if (!isResStatusSubscribed(Response)) {
// turn into a rejection
let e = new Error("Unauthorized Request");
e.status = 401;
throw e;
}
});
}
const isAdmin = function(token) {
return apiGet("/isAdmin", token).then((response) => {
let isAdmin = response.data.Response.is_admin
if (!isAdmin) {
// turn into a rejection
let e = new Error("User is not an Admin");
e.status = 403;
throw e;
}
});
}
// code inside your request handler which you already showed to be async
try {
await Promise.all([isSubscribed(token), isAdmin(token)]);
await insertLiveClassDB(req.body);
return res.status(200).send({ Response: "Success." });
} catch(e) {
let status = e.status || 500;
return res.status(status).send({errorMessage: e.message});
}
变更摘要:
将 isSubscribed()
和 isAdmin()
变成可重复使用的函数,return 是一个承诺。如果他们是订阅者或管理员,该承诺会解决,如果不是,则拒绝,如果 API 有错误,也会拒绝。
如果这些函数获得成功的 API 响应,但显示它们未订阅或未成为管理员,那么它们将拒绝并使用具有消息集的自定义错误对象,并且具有建议响应状态的自定义 属性 .status
集。
如果这些函数没有获得成功的 API 响应(API 调用本身失败),那么它将是 apiGet()
拒绝的任何错误对象与.
然后,await Promise.all([isSubscribed(token), isAdmin(token)])
如果它解决了,那么它通过了两个测试。如果它拒绝,则它至少未通过一项测试,并且拒绝将以先失败者为准。您可以使用 try/catch
捕获该拒绝以及来自 insertLiveClassDB(req.body)
的任何拒绝。 catch
处理程序然后可以在一处发送错误响应,保证您不会尝试发送多个响应。
请注意 isSubscribed()
和 isAdmin()
如何测试他们的响应并将 returned 承诺变成 throw e
的拒绝承诺,如果 API响应表示失败。这允许调用代码在一个代码路径中处理所有类型的故障。
我认为没有必要 Promise.all
,性能提升将是微乎其微的。如果需要,按顺序执行这两项检查并在每项检查之后抛出错误会更容易,或者如果两个条件都通过则最后响应一次。但这是可行的:
const isSubscribed = async () => {
const response = await apiGet("/isSubscribed", token);
if (!isResStatusSubscribed(response)) throw { message : "Unauthorized Request.", status : 401 }; // Will be caught in the Promise.all catch block
}
const isAdmin = async () => {
const response = await apiGet("/isAdmin", token);
if (!isResStatusSubscribed(response)) throw { message : "User is not an Admin", status : 403 }; // Will be caught in the Promise.all catch block
}
(async () => {
try{
await Promise.all([isSubscribed(), isAdmin()]);
await insertLiveClassDB(req.body)
res.status(200).send({ Response: "Success." })
} catch(err) {
res.status(err.status).send({ errorMessage : err.message })
}
})();
Promise.all
只失败一次,只要任何 Promise 失败。因此,即使两个条件都抛出错误,catch
块也只会被触发一次。
如果 isSubscribed 检查失败,则没有必要检查 isAdmin,因此请按顺序进行检查。
通过在检查失败时抛出错误,然后这些或任何其他错误都可以由终端捕获来处理。
我会这样写:
apiGet("/isSubscribed", token)
.then(response => {
if (!isResStatusSubscribed(response)) {
throw Object.assign(new Error('Unauthorized Request'), { 'code':401 }); // throw an Error decorated with a 'code' property.
}
})
.then(() => {
// arrive here only if the isSubscribed check is successful.
return apiGet("/isAdmin", token)
.then(response => {
if (!response.data.Response.is_admin) {
throw Object.assign(new Error('User is not an Admin'), { 'code':403 }); // throw an Error decorated with a 'code' property.
}
});
})
.then(() => {
// arrive here only if the isSubscribed and isAdmin checks are successful.
return insertLiveClassDB(req.body));
}
.then(() => {
// arrive here only if the isSubscribed and isAdmin checks and insertLiveClassDB() are successful.
res.status(200).send({ 'Response': 'Success.' });
})
.catch(e) => {
// arrive here if anything above has thrown.
// e.code will be 401, 403 or undefined, depending of where the failure occurred.
res.status(e.code || 500).send({ 'errorMessage': e.message }); // 500 should be a reasonable default.
};
我正在编写一个代码来检查来自 promise.all 方法中两个不同 API 调用的授权,如果其中任何一个授权失败,将抛出相应的 res.send 方法作为错误,但我的控制台上显示 error : Cannot set headers after they are sent to the client
错误,我哪里出错了?
在屏幕上,显示 res.send 语句,但与此同时,此 error : Cannot set headers after they are sent to the client
错误显示在我的控制台上。我该如何解决这个问题?
我用两种不同的方式编写代码,但每次都显示相同的错误。
第一种方式(没有 .catch):
const isSubscribed = new Promise((resolve, reject) => {
apiGet("/isSubscribed", token).then(async (Response) => {
if (!isResStatusSubscribed(Response)) return res.status(401).send({ errorMessage: "Unauthorized Request." })
})
})
const isAdmin = new Promise((resolve, reject) => {
apiGet("/isAdmin", token).then(async (response) => {
let isAdmin = response.data.Response.is_admin
if (!isAdmin) return res.status(403).send({ errorMessage: "User is not an Admin" })
})
})
Promise.all([isSubscribed, isAdmin]).then(async () => {
await insertLiveClassDB(req.body)
return res.status(200).send({ Response: "Success." })
});
第二种方式(使用 .catch):
const isSubscribed = new Promise((resolve, reject) => {
apiGet("/isSubscribed", token).then(async (Response) => {
if (!isResStatusSubscribed(Response)) reject(res.status(401).send({ errorMessage: "Unauthorized Request." }))
})
})
const isAdmin = new Promise((resolve, reject) => {
apiGet("/isAdmin", token).then(async (response) => {
let isAdmin = response.data.Response.is_admin
if (!isAdmin) reject(res.status(403).send({ errorMessage: "User is not an Admin" }))
})
})
Promise.all([isSubscribed, isAdmin])
.then(async () => {
await insertLiveClassDB(req.body)
return res.status(200).send({ Response: "Success." })
})
.catch(error => {
return error
});
我是表达js和写promise.all方法的新手,真的需要帮助。提前谢谢你。
这里有很多地方出了问题。首先,您可以对每个传入的 http 请求发送一个且仅一个响应。因此,您永远不应该并行启动多个异步操作并让每个操作都发送响应。相反,使用 Promise.all()
跟踪两个异步操作并在 Promise.all()
承诺完成时发送响应。
此外,您还有几个 promise 构造函数示例 anti-pattern,您在其中围绕已经 return 是 promise 的函数包装新的 promise。出于多种原因,这被认为是 anti-pattern。不仅是不必要的额外代码(直接return你已有的promise即可),而且在错误处理上也容易出错
以下是我的建议:
// stand-alone functions can be declared in a higher scope and
// used by multiple routes
const isSubscribed = function(token) {
return apiGet("/isSubscribed", token).then((Response) => {
if (!isResStatusSubscribed(Response)) {
// turn into a rejection
let e = new Error("Unauthorized Request");
e.status = 401;
throw e;
}
});
}
const isAdmin = function(token) {
return apiGet("/isAdmin", token).then((response) => {
let isAdmin = response.data.Response.is_admin
if (!isAdmin) {
// turn into a rejection
let e = new Error("User is not an Admin");
e.status = 403;
throw e;
}
});
}
// code inside your request handler which you already showed to be async
try {
await Promise.all([isSubscribed(token), isAdmin(token)]);
await insertLiveClassDB(req.body);
return res.status(200).send({ Response: "Success." });
} catch(e) {
let status = e.status || 500;
return res.status(status).send({errorMessage: e.message});
}
变更摘要:
将
isSubscribed()
和isAdmin()
变成可重复使用的函数,return 是一个承诺。如果他们是订阅者或管理员,该承诺会解决,如果不是,则拒绝,如果 API 有错误,也会拒绝。如果这些函数获得成功的 API 响应,但显示它们未订阅或未成为管理员,那么它们将拒绝并使用具有消息集的自定义错误对象,并且具有建议响应状态的自定义 属性
.status
集。如果这些函数没有获得成功的 API 响应(API 调用本身失败),那么它将是
apiGet()
拒绝的任何错误对象与.然后,
await Promise.all([isSubscribed(token), isAdmin(token)])
如果它解决了,那么它通过了两个测试。如果它拒绝,则它至少未通过一项测试,并且拒绝将以先失败者为准。您可以使用try/catch
捕获该拒绝以及来自insertLiveClassDB(req.body)
的任何拒绝。catch
处理程序然后可以在一处发送错误响应,保证您不会尝试发送多个响应。请注意
isSubscribed()
和isAdmin()
如何测试他们的响应并将 returned 承诺变成throw e
的拒绝承诺,如果 API响应表示失败。这允许调用代码在一个代码路径中处理所有类型的故障。
我认为没有必要 Promise.all
,性能提升将是微乎其微的。如果需要,按顺序执行这两项检查并在每项检查之后抛出错误会更容易,或者如果两个条件都通过则最后响应一次。但这是可行的:
const isSubscribed = async () => {
const response = await apiGet("/isSubscribed", token);
if (!isResStatusSubscribed(response)) throw { message : "Unauthorized Request.", status : 401 }; // Will be caught in the Promise.all catch block
}
const isAdmin = async () => {
const response = await apiGet("/isAdmin", token);
if (!isResStatusSubscribed(response)) throw { message : "User is not an Admin", status : 403 }; // Will be caught in the Promise.all catch block
}
(async () => {
try{
await Promise.all([isSubscribed(), isAdmin()]);
await insertLiveClassDB(req.body)
res.status(200).send({ Response: "Success." })
} catch(err) {
res.status(err.status).send({ errorMessage : err.message })
}
})();
Promise.all
只失败一次,只要任何 Promise 失败。因此,即使两个条件都抛出错误,catch
块也只会被触发一次。
如果 isSubscribed 检查失败,则没有必要检查 isAdmin,因此请按顺序进行检查。
通过在检查失败时抛出错误,然后这些或任何其他错误都可以由终端捕获来处理。
我会这样写:
apiGet("/isSubscribed", token)
.then(response => {
if (!isResStatusSubscribed(response)) {
throw Object.assign(new Error('Unauthorized Request'), { 'code':401 }); // throw an Error decorated with a 'code' property.
}
})
.then(() => {
// arrive here only if the isSubscribed check is successful.
return apiGet("/isAdmin", token)
.then(response => {
if (!response.data.Response.is_admin) {
throw Object.assign(new Error('User is not an Admin'), { 'code':403 }); // throw an Error decorated with a 'code' property.
}
});
})
.then(() => {
// arrive here only if the isSubscribed and isAdmin checks are successful.
return insertLiveClassDB(req.body));
}
.then(() => {
// arrive here only if the isSubscribed and isAdmin checks and insertLiveClassDB() are successful.
res.status(200).send({ 'Response': 'Success.' });
})
.catch(e) => {
// arrive here if anything above has thrown.
// e.code will be 401, 403 or undefined, depending of where the failure occurred.
res.status(e.code || 500).send({ 'errorMessage': e.message }); // 500 should be a reasonable default.
};