res.status().send()在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错误也会显示在我的控制台上。我该如何解决这个问题?

我用两种不同的方式编写代码,但每次都显示相同的错误。

第1路(无.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构造函数反模式的例子,其中您将一个新的promise包装在一个已经返回promise的函数周围。这被认为是一种反模式,原因有很多。不仅是不必要的额外代码(您可以直接返回您已经拥有的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});
}

变更摘要:

  1. isSubscribed()isAdmin()变成一个可重用的函数,返回promise。如果它们是订阅的或管理的,则该承诺会解决;如果不是,则拒绝;如果API有错误,则拒绝。

  2. 如果这些函数获得了成功的API响应,但显示它们没有订阅或没有管理员,则它们将使用已设置消息的自定义Error对象和已设置建议响应状态的自定义属性.status拒绝。

  3. 如果这些函数没有得到成功的API响应(API调用本身失败(,那么它将是apiGet()拒绝的任何错误对象。

  4. 然后,await Promise.all([isSubscribed(token), isAdmin(token)]),如果它解析,则它通过了这两个测试。如果它拒绝,那么它至少有一项测试失败,拒绝将是先失败的测试。您可以用try/catch捕获拒绝,也可以从insertLiveClassDB(req.body)捕获任何拒绝。catch处理程序可以在一个位置发送错误响应,从而保证您不会尝试发送多个响应。

  5. 请注意,如果API响应指示失败,isSubscribed()isAdmin()如何测试它们的响应,并通过throw e将返回的承诺转换为拒绝承诺。这允许调用代码在一个代码路径中处理所有类型的故障。

如果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,性能提升将是微不足道的。如果需要的话,按顺序进行两次检查并在每次检查后抛出错误会更容易,或者如果两种情况都通过,最后只响应一次。但这是可行的:

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只有一次失败,只要任何承诺失败。因此,catch块将只被触发一次,即使这两个条件都抛出错误。

最新更新