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 } }