Run a function in a loop until - javascript

I'm currently trying to loop running a function.
Can't figure it out and here's what I tried:
do {
queryLastCursor(lastCursor).then(lastCursorResults => {
if (lastCursorResults.hasNext = false) {
hasNextPage = false;
}
console.log(hasNextPage);
})
} while (hasNextPage);
queryLastCursor is a method with a call to an API. When it returns the data it would have a value of hasNext if it returns false then I'd like to set hasNextPage to false. The expected behavior would be that it runs the function again and again until we get the result hasNext = false. Any idea of what am I doing wrong?

If you want to do an async process in a loop, I suggest doing it recursively:
const runQuery = () => {
queryLastCursor(lastCursor)
.then(result => {
if (result.hasNext) {
// recursively call itself if hasNext is true
runQuery();
}
});
}
runQuery();
Assuming you'd want to return some data, you can do:
const runQuery = async (data) => {
return queryLastCursor(lastCursor)
.then(result => {
if (!data) {
data = [];
}
// assuming you are returning the data on result.data
data.push(result.data);
if (result.hasNext) {
// recursively call itself if hasNext is true
return runQuery(data);
}
retun data;
});
}
runQuery()
.then(data => {
// data should be an array of all the data now
});

I would do something like that:
const callApi = async () => {
let result = await someMagic();
while (result.hasNext) {
doSomethingwith(result);
result = await someMagic();
}
return result;
};
const myResult = await callApi();
Though this seems risky, what is we always get a hastNext = true ? Some security seems good, like a limit of loops in the while loop.

Related

How to get return value of a Promise<Array[]> from an Async-Await Function?

Im trying to get the return value of an async-await function but im not getting the value i need.
const filterStuff = async (filters) => {
//some code here
await client<StuffPagination>(endpoint,body)
.then((stuffresult)=>{
const {stuff,page} = stuffresult;
let fiteredStuff as Stuff[] = stuff;
console.log(filteredStuff);
return filteredStuff;
})
.catch(()=>{
//do something
})
.finally(()=>{
//do something
});
};
How do i get the actual return value Stuff[] of the async-await function outside of the function? I have tried using .then to get the data but log for the data is always undefined while the console.log(filteredStuff) that im returning has a value. Is there something im missing?
useEffect(() => {
filterStuff(BaseFilters).then((data) => {
console.log(data);
});
}, []);
You are not returning at required place. Please see below updated code which await for the response.
const filterStuff = async (filters) => {
try {
//some code here
const stuffresult = await client<StuffPagination>(endpoint,body);
const {stuff,page} = stuffresult;
let fiteredStuff as Stuff[] = stuff;
console.log(filteredStuff);
return filteredStuff;
}
catch {
//do something
}
finally {
//do something
}
};
you are missing a return statement in you filterStuff function
const filterStuff = async (filters) => {
//some code here
return client<StuffPagination>(endpoint,body)
.then((stuffresult)=>{
const {stuff,page} = stuffresult;
let fiteredStuff as Stuff[] = stuff;
console.log(filteredStuff);
return filteredStuff;
})
.catch(()=>{
//do something
})
.finally(()=>{
//do something
});
};

Calling a function multiple times in a while loop

I am trying to get data from a function call and I am keeping it in a while loop so the function will be called again and again if data is not received. What I meant with the data is the data1,data2 and data3 arrays that I have returned from the getData function should be populated and not have a null value. But I am not able to find the solution.
Here is my code :
router.get('/someurl' , async(req,res) => {
let data = [];
while(data.length == 0)
{
data = await functionCall()
console.log(data)
}
})
And here is the format of my functionCall code :
const unirest = require("unirest");
const cheerio = require('cheerio')
const getData = async() => {
const data1 = [] , data2 = [] , data3 = [] ;
try {
const response = await unirest
.get('url')
.headers({'Accept': 'application/json', 'Content-Type': 'application/json'})
.proxy("proxy")
const $ = cheerio.load(response.body)
$('hided').each((i,el) =>
{
data1[i] = $(el)
.find('hided')
.text()
data2[i] = $(el)
.find('hided')
})
$('hided').each((i,el) =>
{
data3[i] = $(el)
.find('hided')
.text()
})
if(data1.length && data2.length && data3.length))
{
return [data1 , data2 , data3]
}
}
catch(error)
{
console.log("Error" , error);
}
return [];
}
module.exports = getData
Firstly remove the if statement, as it is literally what the while loop is doing. then remove await, as this is not in an async function, and it will make an error.
Try something like this.
const fetchData = async ()=>{
data = await functionCall()
if(data.length==0){
fetchData()
}else{
return}
}
Assuming that fetchData is an API call there's no reason to initially define data. You can just log the results of calling that API until the results are an empty array.
const data = [1, 2, 3, 4];
// A simple API that returns a reduced array
// on every call
function mockApi() {
return new Promise(res => {
setTimeout(() => {
res([...data]);
data.shift();
}, 1000);
});
}
// Call the API, and if the the response
// has zero length log "No data" otherwise
// log the data, and call the function again.
async function fetchData() {
const data = await mockApi();
if (!data.length) {
console.log('No data');
} else {
console.log(data);
fetchData();
}
}
fetchData();

Array defined outside of function not being populated after awaiting an async call

I have the following code where in the getStuff function I'm making an external async call and storing the result in result. From there I'm looping over result.Content.Ids and pushing the Ids in myArray.
When getStuff() gets called in run(), further down the code I need to access myStuff array but if I console.log it out, it shows up as empty.
const myArray = [];
const getStuff = async (val) => {
const other = {}
if(val) other.newestVal = val;
const result = await someCall(other);
result.Content.Ids.forEach((id) => myArray.push(id));
if (result.AFieldExists) {
getStuff(result.newVal)
}
}
const run = async () => {
await getStuff();
// other code below that uses myArray
// but its empty when I log it
}
run();
I'd have expected myArray to be populated since I'm awaiting getStuff() in run(). What am I doing wrong here?
Your recursive call:
if (result.AFieldExists) {
getStuff(result.newVal)
}
is incorrect, since you're not waiting for the result - only the first getStuff call will be waited for. You need:
const getStuff = async (val) => {
const other = {}
if(val) other.newestVal = val;
const result = await someCall(other);
result.Content.Ids.forEach((id) => myArray.push(id));
if (result.AFieldExists) {
return getStuff(result.newVal)
}
}
You can also clean it up a bit to avoid the ugly outer variable:
const getStuff = async (val, output = []) => {
const other = {}
if (val) other.newestVal = val;
const result = await someCall(other);
output.push(...result.Content.Ids);
return result.AFieldExists
? getStuff(result.newVal, output)
: output;
}
const run = async () => {
const output = await getStuff(); // pass initial value here?
}
you need to put your await calls in try{} catch(){},
and also your recursive call to getStuff() was with out the key word await :
async function someCall() {
return {
Content: {Ids: [1, 2, 3]}
}
}
const myArray = [];
const getStuff = async (val) => {
const other = {}
let result;
if(val) other.newestVal = val;
try{
result = await someCall(other);
}catch(err){
console.log('error from some call' ,err);
}
console.log(myArray)
result.Content.Ids.forEach((id) => myArray.push(id));
console.log(myArray)
if (result.AFieldExists) {
try{
await getStuff(result.newVal)
}catch(err){
console.log('error from get stuff in get stuff' , err);
}
}
}
const run = async () => {
console.log("run")
try{
await getStuff();
}catch(err){
console.log('error' , err);
}
console.log("end run")
console.log(myArray)
// other code below that uses myArray
// but its empty when I log it
}
run();

Repeat async function until true

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

How to wait for each value of an observable with a promise

Let's say I have this observable:
const obs = new Observable((observer) => {
observer.next(0.25);
observer.next(0.75);
observer.next(new ArrayBuffer(100));
observer.complete();
});
How can I wait for each value with a promise?
The following code will only return the last value (value before complete() is called):
const value = await obs.toPromise();
But I want to be able to get each value along the way. I can do something like this:
const value1 = await obs.pipe(take(1)).toPromise();
const value2 = await obs.pipe(take(2)).toPromise();
But that's not ideal, since I'd have to increment the number each time and also take(1000) would still return something in the example, even though there are only 3 values. I'm looking for something like:
const value1 = await obs.pipe(next()).toPromise(); // 0.25
const value2 = await obs.pipe(next()).toPromise(); // 0.75
const value3 = await obs.pipe(next()).toPromise(); // ArrayBuffer(100)
const value4 = await obs.pipe(next()).toPromise(); // null
That is more akin to a generator.
Is there a way to accomplish something like this?
It seems like what you are asking for is a way to convert an observable into an async iterable so that you can asynchronously iterate over its values, either "by hand" or using the new for-await-of language feature.
Heres' an example of how to do that (I've not tested this code, so it might have some bugs):
// returns an asyncIterator that will iterate over the observable values
function asyncIterator(observable) {
const queue = []; // holds observed values not yet delivered
let complete = false;
let error = undefined;
const promiseCallbacks = [];
function sendNotification() {
// see if caller is waiting on a result
if (promiseCallbacks.length) {
// send them the next value if it exists
if (queue.length) {
const value = queue.shift();
promiseCallbacks.shift()[0]({ value, done: false });
}
// tell them the iteration is complete
else if (complete) {
while (promiseCallbacks.length) {
promiseCallbacks.shift()[0]({ done: true });
}
}
// send them an error
else if (error) {
while (promiseCallbacks.length) {
promiseCallbacks.shift()[1](error);
}
}
}
}
observable.subscribe(
value => {
queue.push(value);
sendNotification();
},
err => {
error = err;
sendNotification();
},
() => {
complete = true;
sendNotification();
});
// return the iterator
return {
next() {
return new Promise((resolve, reject) => {
promiseCallbacks.push([resolve, reject]);
sendNotification();
});
}
}
}
Use with for-wait-of language feature:
async someFunction() {
const obs = ...;
const asyncIterable = {
[Symbol.asyncIterator]: () => asyncIterator(obs)
};
for await (const value of asyncIterable) {
console.log(value);
}
}
Use without for-wait-of language feature:
async someFunction() {
const obs = ...;
const it = asyncIterator(obs);
while (true) {
const { value, done } = await it.next();
if (done) {
break;
}
console.log(value);
}
}
that might just work, since take(1) completes the observable, then it is consumed by await, next one in line will produce the second emission to value2.
const observer= new Subject()
async function getStream(){
const value1 = await observer.pipe(take(1)).toPromise() // 0.25
const value2 = await observer.pipe(take(1)).toPromise() // 0.75
return [value1,value2]
}
getStream().then(values=>{
console.log(values)
})
//const obs = new Observable((observer) => {
setTimeout(()=>{observer.next(0.25)},1000);
setTimeout(()=>observer.next(0.75),2000);
UPDATE: Using subject to emit.

Categories

Resources