【问题标题】:'Can't set headers after they are sent to the client' NodeJS error when iOS Swift app is sent to the background当 iOS Swift 应用程序发送到后台时,“无法设置标题后发送到客户端”NodeJS 错误
【发布时间】:2021-01-15 13:00:01
【问题描述】:

我有一个带有 Swift/SwiftUI 的应用程序。我使用 ObservableObjectJSONDecoder 从我的 Node.JS Express API 获取值以显示在我的应用程序中:

struct DevicesList: Decodable {
    var data: [PhilipsHueDevicesListEntry]
}

struct DevicesListEntry: Decodable {
    var ID: String
    var Name: String
    var State: Bool
    var Reachable: Bool
}

从 API 获取值并放入上述对象的类:

class GetDevices: ObservableObject {

var didChange = PassthroughSubject<GetDevices, Never>()

var DevicesList = DevicesList(data: []){
    didSet {
        didChange.send(self)
    }
}

init(){
    let keychain = KeychainSwift()
    let authToken = keychain.get("authToken")!
    let bridgeID = keychain.get("hubID")!
    var request = URLRequest(url: URL(string: GlobalVariables.HUEGetDevices)!)
    request.httpMethod = HTTPMethod.get.rawValue
    let headers = [
        "auth-token": authToken,
        "bridgeID": bridgeID,
        "Content-Type": "application/json"]
    request.allHTTPHeaderFields = headers
    URLSession.shared.dataTask(with: request) { (data, _, _) in
        guard let data = data else {return}
        let devices = try! JSONDecoder().decode(DevicesList.self, from: data)
        DispatchQueue.main.async {
            self.DevicesList = devices
        }
    }.resume()
}

然后在我的应用程序的主屏幕中显示:

@State var devices = GetDevices()

ForEach(devices.DevicesList.data, id: \.ID) { device in
                        ZStack{
                            Rectangle()
                                .cornerRadius(5)
                                .frame(width: 100, height: 100)
                                .foregroundColor(Color.orange)
                            VStack(alignment: .leading){
                                Text(device.Name)
                                    .multilineTextAlignment(.center)
                                    .frame(width: 100, height: 100)
                            }
                        }
....

一切运行良好,直到我向上滑动以将应用程序发送到后台(我假设它再次运行 init() 函数以更新 observableObject)。我收到此错误:

_http_outgoing.js:503
throw new errors.Error('ERR_HTTP_HEADERS_SENT', 'set');
^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at validateHeader (_http_outgoing.js:503:11)
    at ServerResponse.setHeader (_http_outgoing.js:510:3)
    at ServerResponse.header (/app-c/node_modules/express/lib/response.js:771:10)
    at ServerResponse.send (/app-c/node_modules/express/lib/response.js:170:12)
    at ServerResponse.json (/app-c/node_modules/express/lib/response.js:267:15)
    at ServerResponse.send (/app-c/node_modules/express/lib/response.js:158:21)
    at lightDataCallbackFunction (/app-c/app.js:185:32). // <----- This is where the error occurs in my js file
    at Socket.socket.on (/app-c/app.js:121:7)
    at Socket.emit (events.js:180:13)
    at /app-c/node_modules/socket.io/lib/socket.js:528:12
    at process._tickCallback (internal/process/next_tick.js:176:11) 

发生错误的我的JS文件:

lightDataCallbackFunction = function(data){
    if(data['socketID'] == socketID){
      if(data['code'] == '208'){
        return res.status(200).send(data) // <--- OCCURS HERE
      }else{
        return res.status(400).send({"data": "", "code": "209", "message": data['message']})
      }
    }
  }

我真的不知道为什么会这样以及发生了什么。我最好的猜测是,当我将应用程序发送到后台时,可观察对象实例再次运行,并且以某种方式相同的服务器端代码试图向我的应用程序发送另一个(相同的)响应?我什至返回响应以退出函数,所以我不明白为什么会发生这种情况?

编辑:

我从下面尝试过 MwcsMac 评论,他提到他通常根本不使用 init() 函数,而是将要进入 init() 函数的代码移动到一个新函数中,然后在通过.onAppear() 加载相关视图时调用该函数。

我已经尝试过了,但是无论出于何种原因,它要么根本不运行该函数,要么它只是不更新​​我的视图(在我的 SwiftUI 代码中填充我的 ForEach 循环。

【问题讨论】:

  • 出于这样的原因,我避免将代码放在init(){ } 中。我将您在init 中的代码放入一个函数中,并从需要它的视图中调用该函数。
  • @MwcsMac 当然,每次应用程序进入后台时它都会调用该函数,什么都没有改变?
  • @MwcsMac 我试过你的方法,回家还是崩溃,如果我在需要的视图中再次调用该函数,它只会立即崩溃应用程序
  • 您是否尝试过仅在.onapper { } 中触发函数?
  • @MwcsMac 是的,我删除了init() 函数,创建了一个新函数并在我认为的onAppear() 中调用了该函数,什么也没发生,该函数甚至由于某种原因没有运行我知道onAppear() 正在工作,因为我在其中放入了打印语句进行测试??

标签: javascript ios node.js swift swiftui


【解决方案1】:

SwiftUI 有 @Observedobject 包装器作为 ObservableObject 的显式设计对,所以让我们使用这个模式。

  1. 使 GetDevices 使用标准发布方法
class GetDevices: ObservableObject {

  @Published var DevicesList = DevicesList(data: [])

  private var isIdle = true
  func load() {
    guard isIdle else { return }
    isIdle = false

    let keychain = KeychainSwift()
    let authToken = keychain.get("authToken")!
    let bridgeID = keychain.get("hubID")!
    var request = URLRequest(url: URL(string: GlobalVariables.HUEGetDevices)!)
    request.httpMethod = HTTPMethod.get.rawValue
    let headers = [
        "auth-token": authToken,
        "bridgeID": bridgeID,
        "Content-Type": "application/json"]
    request.allHTTPHeaderFields = headers
    URLSession.shared.dataTask(with: request) { (data, _, _) in
        guard let data = data else {return}
        let devices = try! JSONDecoder().decode(DevicesList.self, from: data)
        DispatchQueue.main.async {
            self.DevicesList = devices
            self.isIdle = true
        }
    }.resume()
  }
}
  1. 在视图中使用 ObservedObject/ObservableObject
 @ObservedObject var devices = GetDevices()
 //@StateObject var devices = GetDevices()   // << use this for SwiftUI2.0 / iOS14+

 var body: some View {

   List {  // << some top container View

        // ... your dynamic content here

    }
    .onAppear {
       self.devices.load()      // << call here !!
    }
  }

【讨论】:

  • 成功了!但是怎么做?我不明白出了什么问题?你能解释一下为什么以及如何,我可以将赏金奖励给你。另外,由于在列表中我按下了一个按钮来更改状态,当我更改状态时,我将如何调用 load() 函数,例如每当用户按下按钮时强制视图更新?我尝试在按下按钮时调用load() 函数,但是视图只会在我第二次按下它时更新?
  • ObservableObject-ObservedObject 是正确的预期记录模式,但 ObservableObject-State 不是。如果,关于按钮,您的意思是完全重置,那么您可以创建新实例self.devices = GetDevices() 并在按钮操作中再次调用self.devices.load(),或者在GetDevices 类中添加重置函数,这将清理数据数组并再次调用加载.. . 或您自己的变体。
猜你喜欢
  • 2022-01-11
  • 1970-01-01
  • 2021-01-25
  • 1970-01-01
  • 2021-04-07
  • 1970-01-01
  • 2021-03-15
  • 2021-03-30
  • 2020-01-23
相关资源
最近更新 更多