I need to finish some query no sooner than 500ms.
I don't want to waste that 500ms either, like delaying everything like here:
someFunction(someService) {
setTimeout(() => {
getDataFromBackend().then(data => {
someService.makeAnAction();
});
}, 500);
}
How to start getting the data from the backend at once but do something with that data not too soon?
Make a function to get a promise that resolves in the requisite time:
const delay = ms => new Promise(resolve => {
setTimeout(resolve, ms);
});
Wait for both it and your operation to complete:
someFunction(someService) {
return Promise.all([getDataFromBackend(), delay(500)]).then(([data]) => {
someService.makeAnAction();
});
}
Alternative without destructuring, with Bluebird:
someFunction(someService) {
const dataGet = getDataFromBackend();
return delay(500).return(dataGet).then(data => {
someService.makeAnAction();
});
}
Potentially misleading equivalent of the above:
someFunction(someService) {
return delay(500)
.return(getDataFromBackend())
.then(data => {
someService.makeAnAction();
});
}
Async/await:
async someFunction(someService) {
const dataGet = getDataFromBackend();
await delay(500);
const data = await dataGet;
someService.makeAnAction();
}
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 this:
this.toggleWaiting()
this.results = await this.query(term)
this.toggleWaiting()
First a loading spinner gets triggered.
Then the query runs.
And when the query function ended the loading spinner gets closed.
But what if I want to just show the loading spinner, when the query takes maybe more then 0.5 seconds?
Is there a simple way to do this?
A way to achieve this is to pass the this.query(term) promise to a function which will handle triggering the toggleWaiting only when the query takes longer than the specified amount of time (using the timeout).
For example, the below takes a promise, a function (waitingFn) which will be called with the isWaiting status as well as a timeout which you can use to specify how long you want to wait before you show the loading spinner. Finally, when the promise has been fulfilled, we return the result:
async function handleWaiting(promise, waitingFn, timeout) {
let loadingStarted = false;
let timeoutInstance = null;
const timeoutPromise = new Promise((res) => {
timeoutInstance = setTimeout(() => {
loadingStarted = true;
waitingFn(true);
}, timeout);
return res();
});
function onFinished() {
clearTimeout(timeoutInstance);
if (loadingStarted) {
waitingFn(false);
}
}
try {
const [result] = await Promise.all([promise, timeoutPromise]);
onFinished();
return result;
} catch (ex) {
onFinished();
throw ex;
}
}
You can call the handleWaiting function like so:
const result = await handleWaiting(this.query(term), (isWaiting) => this.toggleWaiting(), 500);
As #FZs and #Bergi have pointed out (thank you both), the below is an antipattern due to using the promise constructor:
function handleWaiting(promise, waitingFn, timeout) {
return new Promise((res, rej) => {
let loadingStarted = false;
const timeoutInstance = setTimeout(() => {
loadingStarted = true;
waitingFn(true);
}, timeout);
function onFinished() {
if (loadingStarted) {
waitingFn(false);
}
clearTimeout(timeoutInstance);
}
return promise
.then((result) => {
onFinished();
res(result);
})
.catch((ex) => {
onFinished();
rej(ex);
});
});
}
Thanks to ljbc1994 I found a nice solution.
Inside my alpineJs object - I have this implementation:
{
waiting: false,
async handleWaiting(promise, timeout) {
return new Promise((res, rej) => {
let loadingStarted = false;
const timeoutInstance = setTimeout(() => {
loadingStarted = true;
this.waiting = true;
}, timeout);
const onFinished = () => {
if (loadingStarted) {
this.waiting = false;
}
clearTimeout(timeoutInstance);
}
promise
.then((result) => {
onFinished();
res(result);
})
.catch((ex) => {
onFinished();
rej(ex);
});
});
},
async searchForTerm(term) {
this.results = await this.handleWaiting(this.$wire.query(term), 500);
// do something with the results...
},
}
Pretty straightforward.
For someone who is interested in the full code - here is the commit inside the github repo:
https://github.com/MichaelBrauner/sunfire-form/commit/7c1f8270e107a97b03264f5ddc5c3c3ae6f7cfd7
I have an API to call every 2.5 seconds. Initially, the data inside the response object is null as the database is still updating it through a transaction. But on the subsequent 3rd or 4th try, I get the data. I am writing a reusable function for the same, however I get undefined. My goal is to keep calling the API until I get the value in my path and close the connection. Please advice.
P.S: The below API URL doesnt have any delay, but my private API has.
const getData = (url, path) => {
const interval = setInterval(async () => {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
return result[path]
}
}, 2500)
return clearInterval(interval)
}
getData('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion')
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
JS Fiddle URL
Please advice.
You
return clearInterval(interval); // undefined
If you want to return a Promise which will resolve when the data is available, you could do something like this:
const getData = (url, path) => {
return new Promise((resolve, reject) => {
const interval = setInterval(async() => {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
clearInterval(interval); // Clear the interval
resolve(result.data[path]); // Resolve with the data
}
}, 2500);
});
}
getData('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion')
.then(data => {
console.log(data); // Your data is available here
});
// OR
(async () => {
const version = await getData('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion');
console.log(version);
})();
Its because javascript is asynchronous as above comment is already mentioned. You can use either callbacks or promise in javascript. Here is the code:
const getData = (url, path, cb) => {
const interval = setInterval(async () => {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
clearInterval(interval); //We found it remove the interval
cb(result.data[path]);
}
}, 2500);
};
getData(
"https://api.oceandrivers.com/static/resources.json",
"swaggerVersion",
data => {
console.log("test",data);
}
);
Here is the fiddle: https://jsfiddle.net/7pc4hq6t/3/
You could create an asynchronous delay:
const delay = milliseconds => new Promise(resolve, setTimeout(resolve, milliseconds));
Then use like this:
const getDataAsync = async (url, path) => {
while (true) {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
return result.data[path];
}
await delay(2500);
}
}
const data = await getDataAsync('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion');
This avoids the multiple layers of nested callbacks, and produces much more readable code.
This question already has answers here:
Combination of async function + await + setTimeout
(17 answers)
Closed 7 months ago.
I have a async function that waits for an axios call to complete before proceeding. The problem is that I need to put a timeout on the axios call to half a second so that I don't hit the shopify API call limit.
async function processMatchingSchools(customer_metafield_url) {
for (const item of customer_metafield_url) {
await axios.get(item).then((res) => {
for (key in res.data.metafields) {
if (res.data.metafields[key].value === schoolName) {
id_for_each_student.push(shopifyAdmin + "/customers/" + res.data.metafields[key].owner_id + "/metafields.json")
}
}
})
}
console.log("Customer metafields to search", id_for_each_student)
processOwnerIds(id_for_each_student)
}
when I try putting a setTimeout, it calls the setTimeout and moves on before completing the axios calls.
async function processMatchingSchools(customer_metafield_url) {
for (const item of customer_metafield_url) {
await setTimeout(function(item) {
axios.get(item).then((res) => {
for (key in res.data.metafields) {
if (res.data.metafields[key].value === schoolName) {
id_for_each_student.push(shopifyAdmin + "/customers/" + res.data.metafields[key].owner_id + "/metafields.json")
}
}
})
}, 500)
}
console.log("Customer metafields to search", id_for_each_student)
processOwnerIds(id_for_each_student)
}
Any help?
await only works on promises.
You need to wrap setTimeout in a promise:
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
await waitFor(500);
setTimeout() doesn't return a Promise, but you can wrap it in one like this. I also cleaned up the rest of your code a little.
async function processMatchingSchools(customer_metafield_url) {
for (const item of customer_metafield_url) {
await new Promise(resolve => {
setTimeout(resolve, 500)
})
await axios.get(item).then((res) => {
Object.values(res.data.metafields).filter(
({ value }) => value === schoolName
).forEach(({ owner_id }) => {
id_for_each_student.push(`${shopifyAdmin}/customers/${owner_id}/metafields.json`)
})
})
}
console.log("Customer metafields to search", id_for_each_student)
processOwnerIds(id_for_each_student)
}
setTimeout does not return a promise so cannot be awaited.
You could create your own promise-based setTimeout and use that.
const setTimeoutPromise = timeout => new Promise(resolve => {
setTimeout(resolve, timeout);
});
await setTimeoutPromise(500);
Create a sleep function that returns a promise that you can use, like so:
const sleep = (milliseconds=500) => new Promise(resolve => setTimeout(resolve, milliseconds))
And to use it in an async function:
(async () => {
console.log("function invoked...")
await sleep(500)
console.log("I got here about 500 milliseconds later")
})()
You need to to create new promise for example like that
function delay(ms){
return new Promise(resolve => setTimeout(resolve, ms))
}
And then use it in your code before calling API
...
await delay(500)
await axios.get(item).then((res) => {
...
I created setTimeout2 function that works the same just as a promise:
const setTimeout2 = (callback, ms) => {
return new Promise(resolve => setTimeout(() => {
callback();
resolve();
}, ms));
}
So, altogether (noticed the setTimeout2 change):
async function processMatchingSchools(customer_metafield_url) {
for (const item of customer_metafield_url) {
await setTimeout2(function(item) {
axios.get(item).then((res) => {
for (key in res.data.metafields) {
if (res.data.metafields[key].value === schoolName) {
id_for_each_student.push(shopifyAdmin + "/customers/" + res.data.metafields[key].owner_id + "/metafields.json")
}
}
})
}, 500)
}
console.log("Customer metafields to search", id_for_each_student)
processOwnerIds(id_for_each_student)
}
I'm using await within an async function execute functions in a particular order, if you see here - I wanted startAnim to wait until hideMoveUI had finished executing to execute itself.
Though my console log returns:
startAnim
hideMoveUI
My code:
async function printAll() {
await hideMoveUI();
await startAnim();
}
printAll();
hideMoveUI = () => {
setTimeout(() => {
console.log('hideMoveUI');
}, 3000);
}
startAnim =() => {
setTimeout(() => {
console.log('startAnim');
}, 500);
}
Is setTimeout an async function?
How can I make the second function wait for the first one to finish? any help or advice is appreciated. Thank you in advance.
Two issues:
Your hideMoveUI/startAnim functions have no return value, so calling them results in undefined. await undefined is undefined.
If you fix #1, await would be waiting on a timer handle, which on browsers is a number. There's no way for await to know that number is a timer handle.
Instead, give yourself a promise-enabled setTimeout and use it.
E.g.:
const wait = (delay, ...args) => new Promise(resolve => setTimeout(resolve, delay, ...args));
const hideMoveUI = () => {
return wait(3000).then(() => console.log('hideMoveUI'));
};
const startAnim = () => {
return wait(500).then(() => console.log('startAnim'));
};
async function printAll() {
await hideMoveUI();
await startAnim();
}
printAll()
.catch(e => { /*...handle error...*/ });
or of course
const wait = (delay, ...args) => new Promise(resolve => setTimeout(resolve, delay, ...args));
const hideMoveUI = async () => {
await wait(3000);
console.log('hideMoveUI');
};
const startAnim = async () => {
await wait(500);
console.log('startAnim');
};
async function printAll() {
await hideMoveUI();
await startAnim();
}
printAll()
.catch(e => { /*...handle error...*/ });