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?
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();
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();
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
});
Related
I'm attempting to setup an async function so that my next step will not start until the function finishes.
I coded one module to connect to mongodb server, and then check to see if it's connected. These two functions work well together.
const mongoose = require('mongoose');
const mongoServer = `mongodb://127.0.0.1/my_database`;
const consoleColor = { green: '\x1b[42m%s\x1b[0m', yellow: '\x1b[43m%s\x1b[0m', red: '\x1b[41m%s\x1b[0m' }
exports.connectMongoose = () => {
mongoose.connect(mongoServer, { useNewUrlParser: true });
}
exports.checkState = () => {
const mongooseState = mongoose.STATES[mongoose.connection.readyState];
return new Promise((resolve) => {
if (mongooseState === 'connected') {
console.log(consoleColor.green, `Mongoose is ${mongooseState}.`);
resolve();
} else if (mongooseState === 'connecting') {
console.log(`Mongoose is ${mongooseState}.`);
setTimeout(() => {
this.checkState();
}, 1000);
} else {
console.log(consoleColor.red, `Mongoose is ${mongooseState}.`);
}
});
}
The next thing I tried to do was connect to the mongo db using my connectMongoose function, and then call a second function that will run my checkState function, and only perform the next function if it resolves (the if statement for the "connected" state.
const dbconfig = require('./dbconfig')
dbconfig.connectMongoose()
const testAwait = async () => {
await dbconfig.checkState();
console.log("Do this next");
}
testAwait()
The testAwait function runs, but it does not get to the console.log function which leads me to believe I'm doing something wrong when passing the resolve.
setTimeout(() => {
this.checkState();
}, 1000);
When this block is hit, the promise is never resolved. The original promise needs to resolve (as your code is currently, if the status is connecting, a new promise is created, but nothing waits for it, and the original promise never resolves). You could go with a pattern like this:
let attempts = 0;
const isConnected = async () => {
console.log("checking connection state...");
attempts++;
if (attempts >= 5) {
return true;
}
return false;
}
const wait = ms => new Promise(res => setTimeout(res, ms));
const checkState = async () => {
while (!(await isConnected())) {
await wait(1000);
}
return;
};
checkState().then(() => console.log("done"));
But to keep it more in line with what you've written, you could do:
const checkState = () => {
const mongooseState = Math.random() > 0.2 ? "connecting" : "connected";
return new Promise((resolve) => {
if (mongooseState === 'connected') {
console.log(`Mongoose is ${mongooseState}.`);
resolve();
} else if (mongooseState === 'connecting') {
console.log(`Mongoose is ${mongooseState}.`);
setTimeout(() => {
checkState().then(resolve);
}, 1000);
}
});
}
checkState().then(() => console.log("done"));
I think the issue here in the above code, you are only resolving your promise once. There is no rejection either. Thus, your code is blocked inside the promise. See the below example. You should exit the promise in any case resolve or reject.
const random = parseInt(Math.random());
const testAwait =
async() => {
await new Promise((resolve, reject) => {
if (random === 0) {
resolve(random);
} else {
reject(random);
}
});
console.log("Do this next");
}
testAwait()
I have a function which accepts a string and a path value and checks whether the path at the result returns a 1 or a -1. I fire the function with multiple requests and everything seems to be successful except for one. For example, if I call the function with 10 different URL's continously (one by one, not in an array), the promise is resolved for 9 but not for the 10th one.
This is my code:
enum Status {
Queued = 0,
Started = 1,
Finished = 2,
Failed = -1,
}
let dataFetchingTimer: number;
export const getDataAtIntervals = (url: string, path: string): Promise<any> => {
clearTimeout(dataFetchingTimer);
return new Promise<any>((resolve: Function, reject: Function): void => {
try {
(async function loop() {
const result = await API.load(url);
console.log(`${url} - ${JSON.stringify(result)}`)
if (
get(result, path) &&
(get(result, path) === Status.Finished ||
get(result, path) === Status.Failed)
) {
return resolve(result); // Resolve with the data
}
dataFetchingTimer = window.setTimeout(loop, 2500);
})();
} catch (e) {
reject(e);
}
});
};
export const clearGetDataAtIntervals = () => clearTimeout(dataFetchingTimer);
Please advice. In the above image, 4535 is called only once. and is not called until 2 or -1 is returned.
Using a single timeout for all your calls might be the cause of weird behaviors. A solution to avoid collisions between your calls might be to use a timeout per call. You could do something along these lines (I used simple JS because I'm not used to Typescript):
const Status = {
Queued: 0,
Started: 1,
Finished: 2,
Failed: -1,
}
let dataFetchingTimerMap = {
// Will contain data like this:
// "uploads/4541_status": 36,
};
const setDataFetchingTimer = (key, cb, delay) => {
// Save the timeout with a key
dataFetchingTimerMap[key] = window.setTimeout(() => {
clearDataFetchingTimer(key); // Delete key when it executes
cb(); // Execute the callback
}, delay);
}
const clearDataFetchingTimer = (key) => {
// Clear the timeout
clearTimeout(dataFetchingTimerMap[key]);
// Delete the key
delete dataFetchingTimerMap[key];
}
const getDataAtIntervals = (url, path) => {
// Create a key for the timeout
const timerKey = `${url}_${path}`;
// Clear it making sure you're only clearing this one (by key)
clearDataFetchingTimer(timerKey);
return new Promise((resolve, reject) => {
// A try/catch is useless here (https://jsfiddle.net/4wpezauc/)
(async function loop() {
// It should be here (https://jsfiddle.net/4wpezauc/2/)
try {
const result = await API.load(url);
console.log(`${url} - ${JSON.stringify(result)}`);
if ([Status.Finished, Status.Failed].includes(get(result, path))) {
return resolve(result); // Resolve with the data
}
// Create your timeout and save it with its key
setDataFetchingTimer(timerKey, loop, 2500);
} catch (e) {
reject(e);
}
})();
});
};
const clearGetDataAtIntervals = () => {
// Clear every timeout
Object.keys(dataFetchingTimerMap).forEach(clearDataFetchingTimer);
};
The Problem is with the uplines.push.
I always get an empty uplines array so the last part of the code doesn't run. The promises resolve later and I get the correct data. May I know how to go about doing it the correct way?
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
uplines = uplines.filter(
(v, i) => uplines.findIndex((index) => index === v) === i
); //remove duplicates
uplines.forEach((user) => {
if (user.chatId) {
sendTelegramMessage(user.chatId, saleToDisplay, currentUser.displayName);
console.log("Telegram Message Sent to " + user.displayName);
} else {
console.log(user.displayName + " has no chatId");
}
});
There are a few things that you have missed out while implementing the async call, which are explained in the inline comments in the code snippet.
A short explanation for what happened in your code is that in the line sale.rens.forEach you are passing an async function in the argument, which does not make any difference to the function forEach, it will execute it without waiting for it to complete.
Therefore in my answer I am using Promise.all to wait for all the async function calls to complete before returning the result.
// This is wrapped in an immediately executed async function because await in root is not supported here
(async () => {
const mockGetData = () => new Promise(resolve => setTimeout(resolve, 1000));
const sale = {
rens: [
{ userFid: 1 },
{ userFid: 2 },
{ userFid: 3 }
]
};
const getAllUplines = async () => {
const uplines = [];
const findUser = async (userFid) => {
// Simulating an async function call
const userDoc = await mockGetData();
console.log("User data received");
uplines.push(`User ${userFid}`);
};
const promises = [];
sale.rens.forEach(ren => { // This function in foreach does not have to be declared as async
// The function findUser is an async function, which returns a promise, so we have to keep track of all the promises returned to be used later
promises.push(findUser(ren.userFid));
});
await Promise.all(promises);
return uplines;
};
let uplines = await getAllUplines();
console.log(uplines);
})();
In order to get the results of getAllUplines() properly, you need to add await to all async functions called in getAllUplines().
const getAllUplines = async () => {
uplines = [];
const findUser = async (userFid) => {
const userDoc = await firestore.collection("users").doc(userFid).get();
if (userDoc.exists) {
const user = { ...userDoc.data(), id: userDoc.id };
console.log(user);
uplines.push(user);
if (user.immediateUplineFid) {
await findUser(user.immediateUplineFid); //self looping
}
} else {
console.log("No User Found");
return null;
}
};
sale.rens.forEach(async (ren) => {
await findUser(ren.userFid);
});
console.log(uplines);
return uplines;
};
I have the following code on which I am trying to block the execution of the method _saveAddress multiple time, so I made a promise for this method.
const [pressEventDisabled, setPressEventDisabled] = useState(false);
<TouchableOpacity style={style.button_container} activeOpacity={1} disabled={pressEventDisabled} onPress={async () => {setPressEventDisabled(true); await _saveAddress(); setPressEventDisabled(false);}} >
The problem is that I want to resolve the promise after the callback method it's executed. It's there any way to wait for the dispatch function to execute or to resolve the promise inside the callback method?
This is the method for saving the address:
const _saveAddress = () => new Promise(async (resolve) => {
var valid = _validate();
if (valid) {
const address = createAddressJson();
if (addressId) {
var addressIdProperty = {
id: addressId
};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, _onAddressSaveEditCallback));
} else {
dispatch(addAddress(address, _onAddressSaveEditCallback));
}
} else {
//notify
notifyMessage(strings.fill_required_inputs_validation);
resolve();
}
});
This is the callback method:
const _onAddressSaveEditCallback = async (success: boolean, apiValidations: any, address ? : Address, ) => {
if (success) {
if (typeof callback == 'function') {
callback(address);
}
await Navigation.pop(componentId);
} else {
setDataValidations(apiValidations);
}
};
Just do exactly what you say in the title. Nothing more, nothing less:
if (addressId) {
var addressIdProperty = {id: addressId};
const newAddress = Object.assign(addressIdProperty, address);
dispatch(editAddress(newAddress, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
} else {
dispatch(addAddress(address, async (s,v,a) => {
await _onAddressSaveEditCallback(s,v,a);
resolve();
}));
}
Of course, since you are passing async () => {} to addAddress instead of _onAddressSaveEditCallback you have to call _onAddressSaveEditCallback yourself since addAddress will be calling the async () => ...
But mixing promises and callbacks like this isn't great. It leads to weird looking and sometimes confusing code. A better solution is to promisify addAddress:
function addAddressPromise (address) {
return new Promise((resolve, reject) => {
addAddress(address, (success, validations, address) {
if (success) return resolve(address);
else reject(validations)
});
});
}
Now you can wait for addAddress:
const _saveAddress = async () => {
// Don't create new promise here, we do it in addAddress..
// ...
let result = await addAddressPromise(address);
dispatch(result);
await _onAddressSaveEditCallback();
// ...
}
First of all I am new to async functions. Where is the problem in this code? I just get test 1 and test 2 output.
let isAvailable = false;
console.log('test1');
let asynchFunc = () => {
return new Promise(() => {
if (isAvailable === true) {
console.log('here asyncFunc');
}
});
}
(async () => {
await asynchFunc();
});
console.log('test2');
isAvailable = true;
I don't know if i say it in a better way:
If you add a timeout inside the promise, isAvailable = true instruction will get executed before reaching the check on this variable, otherwise, isAvailable will remain false.
You can test both cases below
Without timeout
let isAvailable = false;
console.log('test1');
let asynchFunc = () => {
return new Promise((resolve, reject) => {
if (isAvailable === true) {
console.log('here asyncFunc');
resolve('has been available');
} else {
reject('still not available')
}
});
}
(async () => {
try {
let res = await asynchFunc();
console.log(res);
} catch (e) {
console.log(e);
}
})();
console.log('test2');
isAvailable = true;
With Timeout
let isAvailable = false;
console.log('test1');
let asynchFunc = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (isAvailable === true) {
console.log('here asyncFunc');
resolve('has been available');
} else {
reject('still not available')
}
}, 2000);
});
}
(async () => {
try {
let res = await asynchFunc();
console.log(res);
} catch (e) {
console.log(e);
}
})();
console.log('test2');
isAvailable = true;
Did you mean to call the async function like this?
(async () => {
await asynchFunc();
})();
(Note the added () at the end)
I'm not sure what you're trying to test here, the async function will still see the false value. You can see that by changing your function to:
let asynchFunc = () => {
return new Promise(() => {
console.log('check isavailable:', isAvailable);
});
}
If you're just looking for a setup that will let you play around with async functions then you can use this code:
const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));
let isAvailable = false;
(async () => {
console.log('isAvailable:', isAvailable);
await sleep(1000);
console.log('isAvailable:', isAvailable);
})();
console.log('test2');
isAvailable = true;
Just be aware that using a sleep function like this to wait for something to be available is completely the wrong way of doing things. You should never have an isAvailable flag, the value should just be a promise that will resolve once it's available. What language are you coming from? What is your actual issue? We can help you better with real world problems.
Here's a function you can play around with that might help you understand what's going on a bit more. Note you're using a immediately invoked function expression (IIFE) to switch from synchronous to asynchronous code, which is fine as long as you understand that console.log('test2') in your original code may not process in the same order as your code looks like it might. However, you did not call the IIFE, so simply add () to the end of it.
I.e.,
(function (){})();
(()=>{})();
(async function(){})();
(async ()=>{})();
As for the rest of your code, you don't resolve or reject your promise, return the promise correctly from the function, and you need to try{}catch{} your asynchronous function otherwise reject() from the promise will be unhandled.
(function(){
let isAvailable = false;
let asynchFunc = () => new Promise((resolve,reject) => {
if (isAvailable === true) {
console.log('Available');
resolve(true);
}else{
console.log('Not available');
reject(false)
}
});
(async () => {
try{
// isAvailable = true; // test with me.
console.log('test1');
// result is always true, unless there is an error.
// but if there is an error
// the rejected promise will go to catch(e){}.
let result = await asynchFunc();
console.log('test2');
isAvailable = true;
}catch(e){
console.log('Error:',e)
}
})();
})();
let isAvailable = false;
console.log('test1');
let asyncFunc = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (isAvailable === true) {
resolve('here asyncFunc');
}
}, 2000);
});
}
(async() => {
return await asyncFunc(); // await used here is optional
})().then(v => {
console.log(v); // prints value after 2 seconds.
});
console.log('test2');
isAvailable = true;
All Promises need to be either resolved or rejected. In your case you did neither. In order to see the value in the async function, I passed the value to the resolve callback.
In an async function it is optional to use the await key when returning a value as the return value of an async function is implicitly wrapped in Promise.resolve.