ExpressJS不会等待我的承诺

时间:2017-10-04 20:47:29

标签: javascript express

我正在我的服务器上创建搜索页面。当到达端点并且用户等待搜索功能返回结果并呈现页面时,Express会转到404处理程序,而当我假设调用render函数时,我得到以下错误:

错误:发送邮件后无法设置邮件头。

我做错了什么?

router.get("/", async (req, res) => {
    try {
        const queryString = req.query.q;

        const user = helper.checkAndGetUser(req, res);

        let s = String(queryString), searchedTags = [""];
        if(s.indexOf(",") > -1){
            searchedTags = s.replace(" ", "").split(",");
        }

        const options = {
            "query": {tags: {$all: searchedTags}, _forSale: true}
        };

        const results = await Search.search(options).then(result => result).catch(err => {
            throw err;
        });

        //This res.render -call is called after the 404 splat-route.
        return res.render("partial/search.pug", {user: user, search: {
            query: queryString,
            results: results
        }});

        //If I'd use res.send for debugging, it is instead called before the splat-route, like the following:
        return res.send(results);
    } catch(err) {
        next(err);
    }
});

module.exports = router;

我注册路由器:

const search = require("./search.js");
app.use("/search", search);

其次是404 splat-route:

app.get("*", async (req, res, next) => {

    const user = helper.checkAndGetUser(req, res);

    res.status(404);
    res.render("partial/404.pug", {user: user});
});

澄清: 我的问题是我如何才能像调用res.send函数一样调用res.render函数?

更新[2017-10-05]: 我继续使用该网站的另一部分,一个类似的端点,并发现如果使用res.send而不是res.render,则发送由promise提供的结果按预期工作。使用res.render,404处理程序再次启动。这可能是Express中的错误吗?

2 个答案:

答案 0 :(得分:4)

如果您在发送res之后尝试写入res.render(),则会发生这种情况,因此您必须在return res.render(...)之后调用其他代码,或者在调用之前已经响应。

将其更改为res.render(),以便它退出函数,否则它将继续执行该函数并点击其他(req, res, next)等。

还有一些错误处理程序。我将在几分钟内通过提示(在电话上)更新我的帖子。它应该有return next(err)并调用// these routes occur in the order I show them app.get('/route', async (req, res, next) => { try { const data = 'asdf' const payload = await something(data) .then((result) => createPayload(result)) // remember, if you throw anywhere in try block, it will send to catch block // const something = willFail().catch((error) => { // throw 'Custom error message:' + error.message // }) // return from the route so nothing else is fired return res.render('route', { payload }) } catch (e) { // fire down to error middleware return next(e) } }) // SPLAT app.get('*', async (req, res, next) => { // if no matching routes, return 404 return res.status(404).render('error/404') }) // ERRORS app.use(async (err, req, res, next) => { // if err !== null, this middleware fires // it has a 4th input param "err" res.status(500).render('error/500') // and do whatever else after... throw err }) 并将其传递给您的错误处理中间件。

这是我喜欢在async / await Express中使用的模式:

next()
  

注意:在没有param的情况下调用的anything回调被视为无错误,并进入下一个中间件。如果传入err,它将使用param作为错误处理中间件中return的值来触发错误中间件。只要错误中间件最后出现,您就可以在路由和其他中间件中使用此技术。请注意res.send/render().then()的使用,以防止双重设置标题。

NEW:

有一些东西看起来有点err有一个回调。我没有逻辑地看到.then()来自哪里,因为已解析的承诺的值作为result进入try { let results = []; await Search.search(options).then(result => { results = result; }, err => { throw err; }); console.log("res.render"); return res.render("partial/search.pug", {user: user, search: { query: string, results: results }}); } catch(err) { next(err); } 函数。此时,它是可疑的,如果可能应该删除或重构。这部分在这里:

router.get("/", async (req, res, next) => {

    try {
        const queryString = req.query.q;
        const user = helper.checkAndGetUser(req, res);

        let s = String(queryString), searchedTags = [""];
        if (s.indexOf(",") > -1) {
            searchedTags = s.replace(" ", "").split(",");
        }
        const options = {
            "query": { tags: { $all: searchedTags }, _forSale: true }
        };

        // If a promise is ever rejected inside a try block,
        // it passes the error to the catch block.
        // If you handle it properly there, you avoid unhandled promise rejections.

        // Since, we have async in the route function, we can use await
        // we assign the value of Search.search(options) to results.
        // It will not proceed to the render statement
        // until the entire promise chain is resolved.
        // hence, then(data => { return data }) energizes `results`
        const results = await Search.search(options)
            .then(data => data)
            // If any promise in this chain is rejected, this will fire
            // and it will throw the error to the catch block
            // and your catch block should pass it through to your
            // error handling middleware
            .catch(err => { throw 'Problem occurred in index route:' + err });

        return res.render("partial/search.pug", {
            user: user, search: {
                query: string,
                results: results
            }
        });
    } catch (err) {
        // look at the top how we added next as the 3rd, callback parameter
        return next(err);
    }
});

module.exports = router;

首先,这里是我希望看到的async / await语法:

// notice how we add `err` as first parameter
app.use((err, req, res, next) => {

    const user = helper.checkAndGetUser(req, res);

    res.status(404);
    res.render("partial/404.pug", {user: user});
});

错误处理程序:

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})

来自Express文档:

  

以与其他中间件函数相同的方式定义错误处理中间件函数,除了错误处理函数有四个参数而不是三个:(err,req,res,next)。例如:

next()

http://expressjs.com/en/guide/error-handling.html

这可能是您的真正问题,因为如果使用任何输入调用err,错误处理程序应触发,但您的错误处理器似乎每次都会触发就像一个普通的中间件一样,所以我怀疑是因为该中间件函数没有// code example in docs 参数,所以它被视为正常的。

  

默认错误处理程序

     

Express附带内置错误处理程序,可处理应用程序中可能遇到的任何错误。此缺省错误处理中间件函数将添加到中间件函数堆栈的末尾。

     

如果您将错误传递给next()并且您没有在错误处理程序中处理它,它将由内置错误处理程序处理;错误将通过堆栈跟踪写入客户端。堆栈跟踪不包含在生产环境中。

     

如果在开始编写响应后调用next()并出现错误(例如,如果在将响应流式传输到客户端时遇到错误),则Express默认错误处理程序将关闭连接并使请求失败。 / p>      

因此,当您添加自定义错误处理程序时,当标题已发送到客户端时,您将希望委托Express中的默认错误处理机制:

app.get('*', async (req, res, next) => {})
  

请注意,如果您多次使用代码中的错误调用next(),即使自定义错误处理中间件已到位,也可能会触发默认错误处理程序。

我还建议在错误处理程序middlware(也就是列表中最后加载的路径)的正上方使用该splat路由next(err)。这将捕获所有不匹配的路由,例如/ sih8df7h6so8d7f并将客户端转发到404.我认为错误处理程序middlware更适合于错误500和清除格式化类型错误,因为它为您提供了一个可以解析{{{{ 1}}随时从路线调用它。

我通常使用JSON Web令牌进行身份验证失败(作为每个auth所需路由中的第一行代码):

if (!req.person) return res.status(403).render('error/403')

我意识到其中一些可能fry your wig wholesale,所以尝试所有这些东西,看看每件作品在确定你是否愿意使用它之前。

答案 1 :(得分:3)

经过几天一次又一次的代码检查后,我偶然发现checkAndGetUser函数中存在问题,即在没有用户登录的情况下运行,并且因为它比异步调用更快。 DB,触发splat端点,从而显示404页面。

我认为res.rennd调用被res.send替换时不触发splat端点的原因是res.send函数比render-call快得多,因为它没有解析任何HTML。

感谢@ agm1984提供有关Express框架的非常有用的信息,如果其他人遇到相同或类似的问题,请务必仔细阅读他的帖子。