Skip to content
Advertisement

How to stop running async function on node.js from react application?

React app can run node.js function which preparing data and sending information to the database in batches. It takes a lot of time and I would like to add the ability to stop this function right from react app.

const getShopifyOrders = require('./shopify');
const getTrack = require('./tracking');
const Order = require('./model');

async function addOrdersToDB(limit) {
  try {
    // Get latest order from DB
    let latestOrd = await Order.findOne().sort('-order_number');

    do {
      // Get Shopify Orders
      let orders = await getShopifyOrders(
        latestOrd ? latestOrd.order_id : 0,
        limit
      );
      latestOrd = orders[0] ? orders[orders.length - 1] : undefined;

      // Update array with tracking status
      let fullArray = await getTrack(orders);

      // Add to DB
      let ins = await Order.insertMany(fullArray, { ordered: false });
      console.log(`Added ${ins.length} entries`);
    } while (latestOrd);
  } catch (err) {
    console.log(err);
  }
}
module.exports = addOrdersToDB;

I tried a lot of things to include in this function including:

  • while loop: added the variable outside the function – if ‘true’ – run code, if not – return – it just doesn’t work (variable was changed from react using socket.IO)

  • setTimeout (also setInterval), triger clearTimeout function from react: this doesn’t work as setTimeout and setInterval doesn’t work in async function

after that:

  • made (actually fond here on stackoverflow) new function to promisify setTimeout to be able to use in async function:
const setTimeout2 = (callback, ms) => {
  return new Promise(
    resolve =>
      (to = setTimeout(() => {
        callback();
        resolve();
      }, ms))
  );
};
async function addOrdersToDB(limit) {
  do {
    await setTimeout2(async () => {
      try {
        // some code here
      } catch (err) {
        console.log(err);
      }
    }, 400);
  } while (latestOrderExist);
}
function clearTO() {
  setTimeout(() => {
    console.log('clearTO');
    clearTimeout(to);
  }, 3000);
}

This for some reason doesn’t iterate.

Is there solution for this? Thanks!

Advertisement

Answer

To abort the do/while loop, you will need to add an additional test to that loop that is some variable that can be modified from the outside world. Also, note that the additional test only works here because you’re using await inside the loop. If there was no await inside the loop, then the loop would be entirely synchronous and there would be no ability to change a variable from outside the loop while the loop was running (because of nodejs’ single-threadedness).

Since this is a server (and globals are generally bad), I will assume we should not use a global. So instead, I would restructure addOrdersToDB() to return a data structure that contains both the promise the existing version returns and an abort() function the caller can call to stop the current processing. This also permits multiple separate calls to addOrdersToDB() to be running, each with their own separate abort() method.

function addOrdersToDB(limit) {
    let stop = false;

    function abort() {
        stop = true;
    }

    async function run() {
        try {
            // Get latest order from DB
            let latestOrd = await Order.findOne().sort('-order_number');

            do {
                // Get Shopify Orders
                let orders = await getShopifyOrders(
                    latestOrd ? latestOrd.order_id : 0,
                    limit
                );
                latestOrd = orders[0] ? orders[orders.length - 1] : undefined;

                // Update array with tracking status
                let fullArray = await getTrack(orders);

                // Add to DB
                let ins = await Order.insertMany(fullArray, { ordered: false });
                console.log(`Added ${ins.length} entries`);
            } while (!stop && latestOrd);

            // make resolved value be a boolean that indicates
            // whether processing was stopped with more work still pending
            return !!(latestOrd && stop);

        } catch (err) {
            // log error and rethrow so caller gets error propagation
            console.log(err);
            throw err;
        }
    }
    return {
        promise: run(),
        abort: abort
    }
}

So, to use this, you would have to change the way you call addOrdersToDB() (since it no longer returns just a promise) and you would have to capture the abort() function that it returns. Then, some other part of your code can call the abort() function and it will then flip the internal stop variable that will cause your do/while loop to stop any further iterations.

Note, this does not stop the asynchronous processing inside the current iteration of the do/while loop – it just stops any further iterations of the loop.

Note, I also changed your catch block so that it rethrows the error so that the caller will see if/when there was an error.

And, the resolved value of the function is the internal stop variable so the caller can see if the loop was aborted or not. A true resolved value means the loop was aborted and there was more work to do.


Here’s an additional version of the function that creates more opportunities for it to stop between await operations within your function and within the loop. This still does not abort an individual database operation that may be in progress – you’d have to examine whether your database supports such an operation and, if so, how to use it.

function addOrdersToDB(limit) {
    let stop = false;

    function abort() {
        stop = true;
    }

    async function run() {
        try {
            // Get latest order from DB
            let latestOrd = await Order.findOne().sort('-order_number');

            if (!stop) {
                do {
                    // Get Shopify Orders
                    let orders = await getShopifyOrders(
                        latestOrd ? latestOrd.order_id : 0,
                        limit
                    );
                    latestOrd = orders[0] ? orders[orders.length - 1] : undefined;
                    if (stop) break;

                    // Update array with tracking status
                    let fullArray = await getTrack(orders);
                    if (stop) break;

                    // Add to DB
                    let ins = await Order.insertMany(fullArray, { ordered: false });
                    console.log(`Added ${ins.length} entries`);
                } while (!stop && latestOrd);
            }

            // make resolved value be a boolean that indicates
            // whether processing was stopped with more work still pending
            return !!(latestOrd && stop);

        } catch (err) {
            // log and rethrow error so error gets propagated back to cller
            console.log(err);
            throw err;
        }
    }
    return {
        promise: run(),
        abort: abort
    }
}
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement