Dealing with error management in Transfer-Encoding chunked HTTP requests using express and axios

My current challenge involves fetching a large amount of data from a database in JavaScript using streaming to avoid loading all the data into memory at once. I am utilizing express as my server and a nodeJS client that retrieves the data using Axios. While I have successfully fetched the data with streaming, I am struggling to handle errors that occur during the streaming process.

Express Server:

app.get('/stream', async (req, res) => {
    try {
      const cursor = //fetch data with a limit & skip (MongoDB)
      while(cursor.hasNext()) {
        const data = await cursor.next()

        const writeToStream = new Promise((resolve) => {
          res.write(data, 'utf8', () => {
            console.log("batch sent");
            resolve()
          })
  
        })
        await writeToStream
      }
      res.end()
    } catch(error) {
      console.log(`error: ${error.message}`)
      
      //How do i send the status & error message to the requestor? 
      //return res.status(400).end(error.message) // <-- wanted behavior
  })

Client:

    try {
      const res = await axios({
        url: 'http://localhost:3000/test',
        responseType: 'stream'
      })

      const { data } = res
      data.pipe(someStream)

      data.on('end', () => { 
        //stream finished
      })

      data.on('error', (error) => { // First Option
        //error without a status or message
        //res.status(error.status).send(error.message) // <-- wanted behavior  
      })
    } catch(error) {  // Second Option
      //error without a status or message
      //return res.status(error.status).send(error.message) // <-- wanted behavior
    }
      

The error handling on the client is functioning properly but I am unsure how to communicate a status and message from the server to the client to indicate and specify an error.

Versions: "axios": "^1.5.1", "express": "^4.18.2"

Your assistance would be greatly appreciated. Thank you in advance!

Answer №1

One important thing to note is that you cannot modify headers after they have already been sent to the client. Therefore, when initiating a stream using res.write, the header with status code 200 has already been sent.

To work around this limitation, a common trick is to establish fixed prefixes for both DATA and ERROR. This way, it becomes possible to differentiate between actual data and error messages. While not the most efficient solution, it proves effective especially since the streaming stack lacks built-in error management mechanisms.

In server.ts:

import express from 'express'

const app = express()
const port = 3000

const DATA_PREFIX = "DATA:";
const ERROR_PREFIX = "ERROR:";

app.get('/stream', async (req, res) => {
  try {
    // Replace MongoDB query with sample data
    const sampleData = ["data1", "data2", "data3", "data4", "data5"]

    for (let dt of sampleData) {
      const writeToStream = new Promise((resolve) => {

        if (dt === "data4") {
          throw new Error("data4 encountered issues.")
        }

        res.write(DATA_PREFIX + dt, 'utf8', () => {
          console.log("Batch sent");
          resolve(true)
        })
      })
      await writeToStream
    }
    
    res.end()
  } catch (error) {
    console.log(`Error: ${error.message}`)

    // How do I send status and error message to the requester?
    res.write(ERROR_PREFIX + error.message)
    res.end()
  }
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

In client.ts:

import axios from 'axios'
import fs from 'fs'
import stream from 'stream'

const DATA_PREFIX = "DATA:";
const ERROR_PREFIX = "ERROR:";

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

const main = async () => {

  await sleep(2000)

  try {
    const res = await axios({
      url: 'http://localhost:3000/stream',
      responseType: 'stream'
    })

    const { data } = res

    const writableStream = fs.createWriteStream('__output__.txt');

    const appendText = new stream.Transform({
      transform(chunk, encoding, callback) {

        const chunkStr = chunk.toString('utf8')

        if (chunkStr.startsWith(DATA_PREFIX)) {
          this.push(Buffer.from(chunkStr.replace(DATA_PREFIX, ""), 'utf8'))
        }
        else if (chunkStr.startsWith(ERROR_PREFIX)) {
          const error = chunkStr.replace(ERROR_PREFIX, "")
          handleErrors(new Error(error))
        }

        callback();
      }
    });

    data.pipe(appendText).pipe(writableStream);

    data.on('end', () => {
      console.log("Stream finished")
    })

    const handleErrors = (error) => {
      console.log("Error during data transmission:", error)
    }

    data.on('error', handleErrors)

  } catch (error) {
    console.log("Error caught:", error)
  }
}

main()

Answer №2

When using the Express Server: Ensuring smooth communication with the client is crucial in your server code. If there are any issues while sending data, it's important to inform the client clearly. Here's how you can achieve this:

  1. To transmit data from the database to the client, we utilize a method known as cursor.forEach().
  2. In case an error occurs during the data transfer process, we will notify the client by acknowledging, "Oops, something went wrong!" and taking responsibility (using HTTP status code 500).
  3. Even if an issue arises before data transmission begins, we will communicate the same message to the client.

Regarding Client Side Handling: On the client side, you have been adept at managing errors, yet let's delve deeper into it:

  1. Upon receiving data from the server, we capture and store it appropriately, such as in a file.
  2. We monitor the completion of data transfer, ensuring all data has been successfully transmitted.
  3. If any difficulties arise during the data retrieval process, we will declare, "Oops, something's wrong!" while logging the incident. Additionally, we have the ability to address the issue effectively, even halting the download if necessary.

Implementing these modifications will enable you to handle data transmission challenges effectively while maintaining clear communication with the client.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can you effectively load shared header and panel HTML onto pages located in separate directories using jQuery Mobile?

I am currently developing a multi-page application utilizing jQuery Mobile and incorporating loadPage() to retrieve various pages. The overall structure is outlined below. landing.html /app-pages/page1.html /app-pages/page2.html /app-pages/page3.html ...

What is the best way to restrict the input year on @mui/x-date-pickers to a certain range?

Is there a way to restrict the input year range when using @mui/x-date-pickers? I want to limit it from 1000 to 2023 instead of being able to enter years like 0000 or 9999. https://i.stack.imgur.com/0p6j3.jpg I have tried adding a mask to InputProps in my ...

Iterating over the local storage to locate a specific item

Utilizing local storage extensively has been beneficial for maintaining state by storing the session on the client side. However, a challenge arises when updating the session. Within my code, there are multiple lists of objects that I serialize in JSON fo ...

Tips for personalizing react-show-more text length with Material-UI Icons

In my SharePoint Framework webpart using React, I am currently utilizing the show more text controller. However, I am interested in replacing the "Show More" and "Show Less" string links with the ExpandMore and ExpandLess Material UI icons. Is there a way ...

Unusual express middleware usage in NodeJS

app.use(function(req,res,next){ console.log('middleware executed'); next(); }); app.get('/1',function(req,res){ console.log('/1'); res.end(); }); app.get('/2',function(req,res){ console.log('/2'); res.end() ...

Issue with Moment.js incorrectly formatting date fields to a day prior to the expected date

Currently, I am attempting to resolve a small issue in my code related to a tiny bug. In my React component, I have set an initial state as follows: const initialFormData = Object.freeze({ date: Moment(new Date()).format('YYYY-MM-DD'), pr ...

Unable to access external library using browserify and debowerify

I'm facing a dilemma with my current setup as I'm dealing with a headache. Here's how things are currently configured: Utilizing bower to acquire vendor libraries (specifically angular) Executing gulp tasks to run browserify Implementing d ...

What is the best way to create a dropdown menu that smoothly slides in from the bottom of the screen?

Is it possible to create dropdown menus in the navigation bar that slide in from the bottom with a smooth transition effect, similar to this example: Although I am working on building my own template, so my code may differ from the original theme. Here is ...

Preventing duplicate submissions in Ajax by serializing form data

I'm having trouble preventing double submissions on my form when I rapidly press the button that triggers a click event. I attempted to disable the button after a successful submission, but it still submits twice. Here is my code: <script> $(do ...

Utilizing puppeteer-core with electron: A guide

I found this snippet on a different Stack Overflow thread: import electron from "electron"; import puppeteer from "puppeteer-core"; const delay = (ms: number) => new Promise(resolve => { setTimeout(() => { resolve(); }, ms); }) ...

How can I prevent ng-blur from triggering when ng-readonly is set to true in AngularJS?

I am currently working with AngularJS and have run into an issue when combining ng-blur with ng-readonly. Even though ng-readonly is set to true, ng-blur still triggers (if the input field is clicked and then somewhere else is clicked). In this example, n ...

The image slide change feature ceases to function properly when incorporating two separate functions

I have a function that successfully changes the image of a container every 5 seconds. However, I am facing an issue when trying to add 2 containers side by side and change their images simultaneously. When I run the functions for both containers, only one ...

able to utilize service within a loop

import { Component, Input, Output, OnInit, OnChanges } from '@angular/core'; import { ViewComponent } from '../view/view.component'; import { HitoService } from '../../services/hito.service'; @Component({ selector: 'ap ...

Retrieve items from an array within a nested MongoDB document

I've been struggling to find a solution for removing objects from arrays within embedded documents in MongoDB. In my schema, I have a user and an array of flower objects structured as follows: const UserSchema = new Schema({ name : { t ...

Navigating and exploring data stored in a mongoose database

Currently, I am attempting to filter data based on a specific id (bWYqm6-Oo) and dates between 2019-09-19 and 2019-09-22. The desired result should return the first three items from the database. However, my current query is returning an empty array. I wou ...

Jquery unresponsive in AJAX form output presentation

I recently delved into the world of jquery and AJAX, and while I grasp most concepts, I'm struggling with a small code snippet. On my webpage, there is a summary of articles. Clicking on an article name triggers a popup window with detailed informati ...

How to extract a variable from a mongoose find method in a Node.js application

Within my Node.js program, I utilize my Mongoose database to query for a specific value in a collection, of which there is only one value present. var myValueX; myCollection.find(function(err, post) { if (err) { console.log('Error ...

Ways to convert JSON object from express into an array

I am looking for a solution on how to tidy up a returned JSON object into an array. My goal is to separate and eliminate any unnecessary punctuation from the recipe_method property of a JSON object retrieved from my database. Below is the data for recipe_ ...

Whenever trying to access `req.body` in Node.js, it appears to be an object initially, but strangely

Integration of expressjs, NodeJs, and Mongodb After logging req.body in the console, it shows up as [object Object]. However, when I log req.body.name, it returns undefined. The data is being sent via POST method using POSTMAN with the following format: { ...

Having trouble with the nav-item dropdown functionality on Laravel Vue?

Currently utilizing Laravel and Vue.js along with AdminLTE. Initially it was functioning properly, but now... head <!-- Font Awesome Icons --> <link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css"> <!-- Theme style --> ...