node-escpos / driver

🖨️ ESC/POS Printer driver for Node.js.
Other
318 stars 32 forks source link

Error: Can't close device with a pending request #92

Open KylianJay opened 7 months ago

KylianJay commented 7 months ago

I've switched to using USB adapter since I thought this would fix my many stability issues. It did not. Instead, I now get this error. What is causing this? `/home/kylianjay/Documents/osozai/osozai-qrmenu-master/pi/node_modules/usb/dist/usb/device.js:80 this.__close(); ^

Error: Can't close device with a pending request at Device.close (/home/kylianjay/Documents/osozai/osozai-qrmenu-master/pi/node_modules/usb/dist/usb/device.js:80:14) at USBAdapter.close (file:///home/kylianjay/Documents/osozai/osozai-qrmenu-master/pi/node_modules/@node-escpos/usb-adapter/dist/index.mjs:133:20) at file:///home/kylianjay/Documents/osozai/osozai-qrmenu-master/pi/node_modules/@node-escpos/core/dist/index.mjs:1159:20 at new Promise () at Printer.close (file:///home/kylianjay/Documents/osozai/osozai-qrmenu-master/pi/node_modules/@node-escpos/core/dist/index.mjs:1158:12)

Node.js v20.11.0 ` I'm on a Raspberry Pi Model 3, for context.

The code in question that causes this issue is here: `app.post('/create/order', async (req, res) => { // we're getting an order! let's print it! console.log("printing order with id: #" + req.body.id) console.log(req.body) let foodString = ""; let drinksString = ""; let dessertsString = "";

if (req.body.items && Array.isArray(req.body.items) && req.body.items.length > 0) {
    const donItems = req.body.items[0].don;
    const drinkItems = req.body.items[0].drinks;
    const dessertsItems = req.body.items[0].desserts;

    if (donItems && Array.isArray(donItems)) {
        const food = donItems.map(donItem => {
            let itemString = donItem.name.replace(/[^\P{L}a-zA-Z]/g, '') + ` `;

            // Check if extraToppings exist and is an array before adding the line
            if (Array.isArray(donItem.selectedToppings) && donItem.selectedToppings.length > 0) {
                const options = donItem.selectedToppings.map(topping => topping.name.replace(/[^\P{L}a-zA-Z\s]/g, ''));
                itemString += `\n Options: (${options.join(', ')})`;
            }

            return itemString;
        });
        foodString = food.join("\n ");
    }

    if (drinkItems && Array.isArray(drinkItems)) {
        const drinks = drinkItems.map(drinkItem => {
            let itemString = drinkItem.name.replace(/[^\P{L}a-zA-Z]/g, '') + ``;

            // Check if milkOptions exist before adding the line
            // Check if extraToppings exist and is an array before adding the line
            if (Array.isArray(drinkItem.selectedToppings) && drinkItem.selectedToppings.length > 0) {
                const options = drinkItem.selectedToppings.map(topping => topping.name.replace(/[^\P{L}a-zA-Z\s]/g, ''));
                itemString += `\n Options: (${options.join(', ')})`;
            }

            return itemString;
        });
        drinksString = drinks.join("\n ");
    }

    if (dessertsItems && Array.isArray(dessertsItems)) {
        const desserts = dessertsItems.map(dessertItem => {
            let itemString = dessertItem.name.replace(/[^\P{L}a-zA-Z]/g, '') + ``;

            // Check if extraToppings exist and is an array before adding the line
            if (Array.isArray(dessertItem.selectedToppings) && dessertItem.selectedToppings.length > 0) {
                const options = dessertItem.selectedToppings.map(topping => topping.name.replace(/[^\P{L}a-zA-Z\s]/g, ''));
                itemString += `\n Options: (${options.join(', ')})`;
            }

            return itemString;
        });
        dessertsString = desserts.join("\n ");
    }

    const separationLine = "-------------------------";

    // Check if there are items for Food before adding the separation line
    if (foodString && (drinksString || dessertsString)) {
        foodString += `\n${separationLine}\n`;
    }

    // Check if there are items for Drinks before adding the separation line
    if (drinksString && dessertsString) {
        drinksString += `\n${separationLine}\n`;
    }
}

try {
    const separationLine = "-------------------------";
    console.log("sending " 
    + "Order ID: " + req.body.id
    + "Table: " + req.body.table
    + "Food: " + foodString
    + "Drinks: " + drinksString
    + "Desserts: " + dessertsString
    + "Total: " + "€" + req.body.priceTotal + ",-"
    + "Date: " + req.body.datetime
    + "Notes: " + req.body.notes
    + "to printer")
    res.status(200).send("Order: " + req.body.id + " Printed")

    const print = () => {
        return new Promise((resolve, reject) => {
            const device = new USB();
            const options = { encoding: "GB18030" /* default */ }
            let printer = new Printer(device, options);
          device.open(function (error) {
            if (error) {
              return reject(error);
            }
            printer
            .font('b')
            .align('ct')
            .size(1,1)
            .text("Receipt 1 of 2")
            .style('bu')
            .size(2, 2)
            .text('New order for Table: ' + req.body.table)
            .text(`\n${separationLine}\n`)
            .text('Food: ' + foodString)
            .text('Drinks: ' + drinksString)
            .text('Desserts: ' + dessertsString)
            .text(`\n${separationLine}\n`)
            .text('Notes: ' + req.body.notes)
            .font('a')
            .size(1, 1)
            .text(`\n${separationLine}\n`)
            .text('Total: ' +  "€" + req.body.priceTotal + ",-")
            .text('Date: ' + req.body.datetime)
            .text('Order ID: ' + req.body.id)
            .text(" ")
            .cut()
            .font('b')
            .align('ct')
            .size(1,1)
            .text("Receipt 2 of 2")
            .style('bu')
            .size(2, 2)
            .text('New order for Table: ' + req.body.table)
            .text(`\n${separationLine}\n`)
            .text('Food: ' + foodString)
            .text('Drinks: ' + drinksString)
            .text('Desserts: ' + dessertsString)
            .text(`\n${separationLine}\n`)
            .text('Notes: ' + req.body.notes)
            .font('a')
            .size(1, 1)
            .text(`\n${separationLine}\n`)
            .text('Total: ' +  req.body.priceTotal)
            .text('Date: ' + req.body.datetime)
            .text('Order ID: ' + req.body.id)
            .text(" ")

            printer
            .cut()
            .close(() => {
                resolve();
            });
          });
        });
      };
      await print();
      console.log('did it get here?')
}

catch (error) {
    console.log(error)
    res.status(400).send("Error printing order: " + req.body.id + " Error: " + error)
}

})`

Again, any help is greatly appreciated!

dosubot[bot] commented 7 months ago

The error "Can't close device with a pending request" typically occurs when trying to close the USB device connection while there are still ongoing or pending I/O operations. This means that your application is attempting to close the connection before all data transfers have finished. To resolve this, you need to ensure that all pending operations are completed or canceled before calling the close method on the device. This might involve adding logic to track ongoing operations and waiting for these to complete before proceeding with the device closure.

Here's a more structured approach to manage printing operations asynchronously, which can help prevent such errors:

const print = (text) => {
  return new Promise((resolve, reject) => {
    device.open(function (error) {
      if (error) {
        return reject(error);
      }
      printer
        .font('a')
        .align('ct')
        .style('bu')
        .size(1, 1)
        .text(text)
        .cut()
        .close(() => {
          resolve();
        });
    });
  });
};

By wrapping your printing logic in a Promise and using async/await, you can ensure that the device is not closed until all operations have been completed. This approach helps in managing the lifecycle of the stream more effectively and reduces the likelihood of encountering errors related to premature closure of the device.

Additionally, ensure that all libraries, especially escpos and its dependencies, are up to date and compatible with your Node.js version (v20.11.0). Incompatibilities can also lead to unexpected errors, including those related to stream and device management.

#### Details **Sources** - [Error: [ERR_STREAM_DESTROYED] Cannot call write after a stream was destroyed.](https://github.com/node-escpos/driver/issues/91) - [packages/usb-adapter/src/index.ts](https://github.com/node-escpos/driver/blob/main/packages/usb-adapter/src/index.ts) - [packages/network-adapter/src/index.ts](https://github.com/node-escpos/driver/blob/main/packages/network-adapter/src/index.ts)
**About Dosu** This response is meant to be useful and save you time. It is **not meant to be a precise solution**, but rather a starting point for your own research. Help me be more useful! Please leave a 👍 if this is helpful and 👎 if it is irrelevant.

To continue the conversation, mention @dosu.