Reputation: 312
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?
Upvotes: 2
Views: 2026
Reputation: 9172
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
});
Upvotes: 1
Reputation: 56875
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();
Upvotes: 1
Reputation: 2101
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();
Upvotes: 1