【问题标题】:ExpressJS doesn't wait for my promiseExpressJS 不会等待我的承诺
【发布时间】:2017-10-04 20:47:29
【问题描述】:

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

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

我做错了什么?

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.render 函数像 res.send 函数一样被调用?

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

【问题讨论】:

  • Express 将等待您的承诺,但看起来您 (1) 可能没有收到它,或者 (2) 您的承诺已解决,但您通过向您的 @ 发送错误数据而获得例外987654324@ 方法。确保userqueryString等存在。
  • 我已经通过登录它们的内容仔细检查了这两个变量是否存在并包含有效数据,每个请求都会调用它。正如预期的那样,就在错误之前。所以这证明了承诺解决了。你能解释一下(1)是什么意思吗?
  • 我需要查看更多代码。那个 404 处理程序应该只是一个 splat 路线,在所有其他路线之后“捕获所有不匹配”app.get('*', (req) => { res.render('404', { req }) })
  • 你想看什么部分?其他路线还是逻辑?
  • 谢谢!我会把它应用到我的代码中,明天试试!

标签: javascript express


【解决方案1】:

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

将其更改为return res.render(...),使其退出函数,否则它将继续通过函数并命中其他res.render()s等。

那个错误处理程序也有问题。我将在几分钟内更新我的帖子(通过电话)。它可能应该有 (req, res, next) 并调用 return next(err) 并将其传递给您的错误处理中间件。

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

// 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
})

注意:在没有参数的情况下调用next() 回调被视为没有错误,并继续执行下一个中间件。如果传入anything,它将触发错误中间件,参数为错误处理中间件中err的值。只要错误中间件最后出现,您就可以在路由和其他中间件中使用此技术。请注意将returnres.send/render() 一起使用,以防止重复设置标题。

新:

.then() 中有一个回调,看起来有点不对劲。我从逻辑上看不出err 的来源,因为已解决的promise 的值作为result 进入.then() 函数。此时,它是可疑的,应尽可能删除或重构。这部分在这里:

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;

错误处理程序:

// 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});
});

来自 Express 文档:

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

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

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

这可能是您真正的问题,因为错误处理程序应该在使用 any 输入调用 next() 时触发,但您的似乎每次都像普通的中间件,所以我怀疑是因为该中间件函数上没有err参数,所以它被视为普通的。

默认错误处理程序

Express 带有一个内置的错误处理程序,可以处理应用程序中可能遇到的任何错误。这个默认的错误处理中间件函数被添加到中间件函数栈的末尾。

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

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

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

// code example in docs

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

我还建议使用错误处理程序中间件上方的 splat 路由 app.get('*', async (req, res, next) => {})(又名作为列表中最后加载的路由)。这将捕获所有不匹配的路由,例如 /sih8df7h6so8d7f 并将客户端转发到您的 404。我认为错误处理程序中间件更适合错误 500 和干净的格式化类型错误,因为它为您提供了一个可以解析 @987654346 值的函数@ 任何时候从路由调用它。

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

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

我知道其中一些可能fry your wig wholesale,所以在确定是否要使用它之前尝试所有这些东西并查看每个部分的工作情况。

【讨论】:

  • 谢谢,但这不是问题所在。我知道这是我收到错误的原因。将不需要的响应发送到 res 的是 404 处理程序。我的问题是如何避免调用 404 处理程序,而是让 Express 等待我的承诺返回?
  • 如果您的示例代码中的res.render() 触发错误,则错误源于//Some logic here 中的区域。
  • 或者错误处理程序也在触发。这也可能导致它。
  • 错误处理程序,即返回的承诺中的第二个函数,没有被调用。并且逻辑工作得很好,否则“结果”参数将不包含正确的数据。
  • 我同意正确处理错误,但您永远不必return res 中间件。小心 Node/Express 中间件中的“返回”,因为它是一个单线程异步进程。
【解决方案2】:

几天后一遍又一遍地检查代码后,我偶然发现了 checkAndGetUser 函数中的一个问题,当在没有用户登录的情况下运行时,并且由于它比对数据库的异步调用更快,触发了splat 端点,因此显示 404 页面。

我相信当 res.render 调用被 res.send 替换时不触发 splat 端点的原因是 res.send 函数比渲染调用快得多,因为它不必解析任何 HTML。

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

【讨论】:

    猜你喜欢
    • 2019-04-29
    • 2019-12-10
    • 1970-01-01
    • 2021-08-29
    • 1970-01-01
    • 1970-01-01
    • 2013-09-16
    • 2018-03-19
    • 1970-01-01
    相关资源
    最近更新 更多