javascript - ExpressJS 不等待我的 promise

标签 javascript express

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

错误:发送后无法设置 header 。

我做错了什么?

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 中的错误吗?

最佳答案

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

将其更改为 return res.render(...) 以便退出函数,否则它将继续执行函数并命中其他 res.render()

错误处理程序也有问题。我将在几分钟内更新我的帖子并提供提示(通过电话)。它可能应该有 (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
})

Note: next() callback called without param is treated as no error, and proceeds to the next middleware. If anything is passed in, it will fire the error middleware with the param as the value of err in the error handling middleware. You can use this technique in routes and other middlewares, as long as the error middleware comes last. Mind your use of return with res.send/render() to prevent double setting headers.

新:

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

首先,这是我希望使用 async/await 语法看到的内容:

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 文档:

Define error-handling middleware functions in the same way as other middleware functions, except error-handling functions have four arguments instead of three: (err, req, res, next). For example:

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

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

The Default Error Handler

Express comes with a built-in error handler, which takes care of any errors that might be encountered in the app. This default error-handling middleware function is added at the end of the middleware function stack.

If you pass an error to next() and you do not handle it in an error handler, it will be handled by the built-in error handler; the error will be written to the client with the stack trace. The stack trace is not included in the production environment.

If you call next() with an error after you have started writing the response (for example, if you encounter an error while streaming the response to the client) the Express default error handler closes the connection and fails the request.

So when you add a custom error handler, you will want to delegate to the default error handling mechanisms in Express, when the headers have already been sent to the client:

// code example in docs

Note that the default error handler can get triggered if you call next() with an error in your code more than once, even if custom error handling middleware is in place.

我还建议在错误处理程序中间件(也称为 last 在您的列表中加载的路线)。这将捕获所有不匹配的路由,例如/sih8df7h6so8d7f 并将客户端转发到您的 404。我认为错误处理程序中间件更适合错误 500 和清除格式化类型错误,因为它为您提供了一个可以解析 next(err) 任何时候从路由调用它。

我通常在使用 JSON 网络 token 的身份验证失败时执行此操作(作为每个需要身份验证的路由中的第一行代码):

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

我意识到其中一些可能会毁了你的假发,所以在你决定是否要使用它之前,先尝试所有这些东西,看看每件作品是否有效。

关于javascript - ExpressJS 不等待我的 promise ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46573900/

相关文章:

javascript - 在 Express 中使用 url 作为 slug

node.js - Mocha,Express 测试错误 - "after all" Hook 错误 - 对象函数没有方法 'close'

javascript - express-fileupload 模块将损坏的图像文件保存到我的硬盘的奇怪问题

javascript - JavaScript 中 [][[]] 和 [[]][] 之间的区别?

node.js - 快速验证器不产生验证错误

javascript - 谷歌应用程序脚本 : how to persist data in spreadsheet between different function calls?

javascript - 如何从扩展栏中删除扩展图标?

node.js - 从字符串表达渲染模板

Javascript单页应用导览工具

JavaScripteach() 不工作