2

I have an async function that checks for the status of an order (checkOrderStatus()). I would like to repeat this function until it returns either "FILLED" or "CANCELED", then use this return value in another function to decide to continue or stop the code. Every order goes through different status before being "FILLED" or "CANCELED", therefore the need to repeat the checkOrderStatus() function (it is an API call).

What I have now is this, to repeat the checkOrderStatus() function:

const watch = filter => {
    return new Promise(callback => {
        const interval = setInterval(async () => {
            if (!(await filter())) return;
            clearInterval(interval);
            callback();
        }, 1000);
    });
};

const watchFill = (asset, orderId) => {
    return watch(async () => {
        const { status } = await checkOrderStatus(asset, orderId);

        console.log(`Order status: ${status}`);

        if (status === 'CANCELED') return false;
        return status === 'FILLED';
    });
};

I then call watchFill() from another function, where I would like to check its return value (true or false) and continue the code if true or stop it if false:

const sellOrder = async (asset, orderId) => {
    try {
        const orderIsFilled = await watchFill(asset, orderId);
        
        if (orderIsFilled) {
            //… Continue the code (status === 'FILLED'), calling other async functions …
        }
        else {
            //… Stop the code
            return false;
        }
    }
    catch (err) {
        console.error('Err sellIfFilled() :', err);
    }
};

However, this does not work. I can see the status being updated in the terminal via the console.log in watchFill(), but it never stops and most importantly, the value in the orderIsFilled variable in sellOrder() does not get updated, whatever the value returned by watchFill() becomes.

How can I achieve the desired behavior?

3

3 Answers 3

1

watch never calls resolve (in the original code, this is misleadingly named callback()) with any value, so there's no way const orderIsFilled = await watchFill(asset, orderId); will populate orderIsFilled with anything but undefined.

If you save the result of await filter() in a variable and pass it to callback as callback(result), your code seems like it should work.

That said, the code can be simplified by using a loop and writing a simple wait function. This way, you can return a value (more natural than figuring out how/when to call resolve), keep the new Promise pattern away from the logic and avoid dealing with setInterval and the bookkeeping that goes with that.

const wait = ms =>
  new Promise(resolve => setTimeout(resolve, ms))
;

const watch = async (predicate, ms) => {
  for (;; await wait(ms)) {
    const result = await predicate();
    
    if (result) {
      return result;
    }
  }
};

/* mock the API for demonstration purposes */
const checkOrderStatus = (() => {
  let calls = 0;
  return async () => ({
    status: ++calls === 3 ? "FILLED" : false
  });
})();

const watchFill = (asset, orderId) =>
  watch(async () => {
    const {status} = await checkOrderStatus();
    console.log(`Order status: ${status}`);
    return status === "CANCELLED" ? false : status === "FILLED";
  }, 1000)
;

const sellOrder = async () => {
  try {
    const orderIsFilled = await watchFill();
    console.log("orderIsFilled:", orderIsFilled);
  }
  catch (err) {
    console.error('Err sellIfFilled() :', err);
  }
};
sellOrder();

Sign up to request clarification or add additional context in comments.

4 Comments

I don't understand why I would need to check for calls and iterate over it in checkOrderStatus, as it is an API call ? Can't I just make the API call and do if (status === 'FILLED') return true; else if (status === 'CANCELED') return false; ?
No, I'm just mocking that function so you can run the example. The if (status... code you mention is still here, I just made it a ternary because I prefer ternaries. As I mentioned in the post, if you just save await filled() in a variable and pass that to callback you should be good to go. The snippet is just a cleanup job and proof-of-functionality.
I tried your solution using my API call in checkOrderStatus but I doesn't work. Is it worth noting that my checkOrderStatus is async and returns a Promise ?
What doesn't work about it, specifically? My checkOrderStatus is async as you can see. If you're struggling to make the existing answers work (this answer is basically the same as mine), maybe the code you've shown us isn't enough of a minimal reproducible example to illustrate what's really going on in your original program.
1

You can use recursive functionality like this:

const checkOrderStatus = async () => {
    // ... function does some work ...
    await someOtherFunction() // you can use here the other async function as well
    // ... function does some more work after returning from await ...
    if(/* if status is FILLED or CANCELED */) {
        // return true or false or some info about response for your needs
    } else {
        checkOrderStatus();
    }
}

// this will response back when status will be FILLED or CANCELED
await checkOrderStatus();

4 Comments

Shouldn't I use await before checkOrderStatus() in the else ?
No you don't need that. Note: since the call stack is empty after await, this means the call stack doesn’t grow with each recursive call. Therefore your call-stack never hits a size limit and the recursion continues as long as it can.
Thanks. I tried using the function as you wrote it, with if (status === 'FILLED') return true; else if (status === 'CANCELED') return false; else { checkOrderStatus() }, but when I try to do const orderIsFilled = await checkOrderStatus(asset, orderId) in my other function, orderIsFilled contains undefined, therefore returning false (the else in sellOrder()), but I can see in a console.log in checkOrderStatus that the status has been filled. Any idea why ?
Without a delay, this doesn't preserve OP's intent. Seems like a lot is left to the imagination.
1

The watch function clears the interval timer after the first call if filter resolves with false. setInterval doesn't wait for an async function to finish executing either so you'll have to create a loop yourself. Try this:

const delay = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds));

const watch = async check => {
    while (true) {
        if (await check()) {
            return;
        }

        await delay(1000);
    }
};

Because watch only resolves when check succeeds, it is not possible to fail so you don't need to check for it (this might be a bug in your code):

const sellOrder = async (asset, orderId) => {
    try {
        await watchFill(asset, orderId);
        
        //… Continue the code (status === 'FILLED'), calling other async functions …
    }
    catch (err) {
        console.error('Err sellIfFilled() :', err);
    }
};

p-wait-for contains an excellent implementation of this. You can use it like so:

import pWaitFor from 'p-wait-for';

const watchFill = (asset, orderId) => pWaitFor(async () => {
    const { status } = await checkOrderStatus(asset, orderId);

    console.log(`Order status: ${status}`);

    if (status === 'CANCELED') return false;
    return status === 'FILLED';
}, {
    interval: 1000,
    leadingCheck: false
});

7 Comments

Thank you for the fast answer. I will give it a try. However, do you have any idea how to implement this in vanilla JS ? Thanks
Is the check() callback supposed to be my checkOrderStatus function ? If so, is the usage like so : while (true) { if (await check() === 'FILLED') return true; else if (await check() === 'CANCELED') return false; } ?
The usage of the watch function is not changed, just the name of the variable. This means your current code that calls the watch function now works.
So I would use watchFill as it is right now but just change watch to the exemple you showed ?
Yup. Just remove your current watch function and replace it with the watch function I provided along with the delay function before it.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.