【问题标题】:Receiving "Error: Can't set headers after they are sent." when making multiple API calls收到“错误:发送后无法设置标头。”进行多个 API 调用时
【发布时间】:2018-04-05 00:58:30
【问题描述】:

我正在重构一个需要进行多个 API 调用的 Express 项目。我目前有 9 个单独的函数来进行每个单独的 API 调用,然后在每个 API 调用完成后将其设置为 res.locals。

我创建了一个数组,其中包含我需要访问的所有 API URL,然后我有一个函数可以遍历该数组以进行所有 fetch 调用。但是,我在设置标头时遇到了问题,以便 JSON 数据通过,因为我收到以下消息:

Error: Can't set headers after they are sent.

根据我设置控制器的方式,我可以获取控制器标头和第一个 API 响应以在我的页面上呈现,或者我只获取控制器标头来呈现。鉴于我的情况,我无法找到解决方案,并且想知道是否有人建议纠正这个问题。我已经粘贴了我认为应该足以解决问题的代码,但是如果我可以提供其他任何内容,请告诉我。

API-Helper 文件(进行 API 调用的地方):

function getData(req, res, next) {
    for (let i = 0; i < links.length; i ++) {
        fetch(links[i])
        .then(res => res.json())
        .then(fetchRes => {
            res.locals.i = fetchRes
            next()
        })        
        .catch(err => {
            res.json({err})
        })
    }
}

以下是从第一个 API 调用和标头中获取数据的控制器:

for (let i = 0; i < 10; i ++) {  
cryptoController.sendApiData = (req, res) => {          
    res.json({
        message: 'data returned for crypto',            
        i: res.locals.i
    })  
    }
}

下面是控制器只返回头部时:

for (let i = 0; i < 10; i ++) {  
cryptoController.sendApiData = (req, res) => {          
    res.json({
        message: 'data returned for crypto',            
        i: res.locals[i]
    })  
    }
}

更新以显示更多代码:

完整的控制器:

// import model
const Crypto = require('../models/Crypto')

// initiate controller object
const cryptoController = {}

// find latest cap coin entry
cryptoController.latest = (req, res, next) => {
    Crypto.findRecent()
    .then(crypto => {
        res.json({
            message: 'retrieved entry',
            data: { crypto }
        })
    }).catch(next)
}

// send api data
// for (let i = 0; i < 10; i ++) {  
cryptoController.sendApiData = (req, res) => { 
    console.log(res.locals)         
    res.json({
        message: 'data returned for crypto',            
        i: res.locals.i
    })  
    // }
}

// create new entry
cryptoController.create = (req, res) => {
    console.log(req.body, ' req.body from cryptoController#create')
    Crypto.create({
        // time made
        time_made: Date.now(),
        // crypto data
        usd: req.body.usd,
        us_high: req.body.us_high,
        us_low: req.body.us_low,
        eur: req.body.eur,
        eur_high: req.body.eur_high,
        eur_low: req.body.eur_low,
        trades: req.body.trades,
        one_hour: req.body.one_hour,
        one_day: req.body.one_day,
        seven_days: req.body.Seven_days,
        crypto_id: req.body.crypto_id
    })
}


module.exports = cryptoController

路线文件:

// import dependencies
const express = require('express')
const cryptoController = require('../controllers/crypto-controller')
const cryptoHelpers = require('../services/crypto-helpers')

// set router variable
const cryptoRouter = express.Router()

// set routes to fetch and store API data
cryptoRouter.post('/crypto', cryptoHelpers.getData, cryptoController.create)
cryptoRouter.get('/crypto', cryptoHelpers.getData, cryptoController.sendApiData)

module.exports = cryptoRouter;

引入路由的服务器文件:

const cryptoRouter = require('./routes/crypto-routes')
app.use('/', cryptoRouter)

模型文件没有被命中,因为此时没有任何东西被插入到数据库中。

【问题讨论】:

  • 您将不得不为此控制器代码(整个请求处理程序以及在该请求处理程序中执行的任何代码)显示更多上下文,因为正如您在此处显示的那样,它没有任何意义。您处于for 循环中,将自定义函数分配给cryptoController.sendApiData,但未执行任何操作。另外,将 10 个不同的函数分配给同一个变量也没有什么好处。只有最后一个将被实际分配。另外,您也没有显示任何实际执行cryptoController.sendApiData() 的代码。
  • 您看到的错误的一般原因是代码针对给定请求发送了多个响应。您只能发送一个响应。例如,对于给定的请求,您只能调用一次res.json()。第一次调用时,将数据转换为 JSON,发送到客户端并关闭 http 连接。无法发送更多数据。
  • 如果我的理解是正确的,我什至无法使用异步函数来做到这一点?
  • 异步函数与它没有任何关系。您确实需要向我们展示更多代码并描述您正在尝试为我们做什么,以便更具体地为您提供帮助。
  • 明白了。我刚刚更新了我的原始帖子以显示路由和完整的控制器文件,以及如何在 server.js 文件中启动路由。澄清一下:我目前有 9 个 API 调用正在获取它们自己的函数(总共 9 个函数)。我正在尝试重构,以便可以在一个函数中进行所有 API 调用,以减少编写的代码量。非常感谢您的回复,如果我可以发送任何其他信息/说明,请告诉我。

标签: node.js api express


【解决方案1】:

好的,这里有一个问题:

function getData(req, res, next) {
    for (let i = 0; i < links.length; i ++) {
        fetch(links[i])
        .then(res => res.json())
        .then(fetchRes => {
            res.locals.i = fetchRes
            next()
        })        
        .catch(err => {
            res.json({err})
        })
    }
}

您在循环中调用next()。完成所有操作后,您只能调用一次next()。由于这些服务器端 fetch() 调用看起来可以并行完成,因此您可以使用 Promise.all() 查看它们何时全部完成。

那么,您在将数据放入res.locals 时也会遇到问题。您只是在循环中分配相同的 res.locals.i 变量,这并没有真正做任何特别有用的事情。

我认为您将不得不为数据使用一个数组,然后更改稍后在路由链中使用res.locals 的代码以访问该数组。

function getData(req, res, next) {
    let promises = links.map(function(link) {
        return fetch(link).then(res => res.json())
    });
    Promise.all(promises).then(results => {
        res.locals.data = results;    // res.locals.data is an array of link json
        next();
    }).catch(err => {
        res.status(500).json({err});
    });
}

然后,在您的 .get() 路由处理程序中,我不知道您要达到的结果类型的确切原因,但是要返回结果数组,您可以执行以下操作:

cryptoController.sendApiData = (req, res) => { 
    console.log(res.locals.data)         
    res.json({
        message: 'data returned for crypto',            
        data: res.locals.data
    }); 
}

【讨论】:

  • 这成功了!非常感谢您对此的帮助,我对 Promises 有一段时间真的很困惑,但这个解释有点让它点击。
猜你喜欢
  • 1970-01-01
  • 2017-10-17
  • 2019-03-26
  • 2020-01-31
  • 1970-01-01
  • 1970-01-01
  • 2018-06-08
  • 2018-06-29
  • 2013-12-22
相关资源
最近更新 更多