I have used await keyword in the main function to wait for the completion of async function call to poll() and yet the function call to my_plot is made before the completion of the poll() function.
async function main() {
getParametersData()
await poll()
my_plot()
}
async function getData() {
const response = await fetch(API)
const message = await response.json()
return message
}
async function poll(count = 1) {
console.log(`Polling ${count}`);
try {
const data = await getData();
if (data && Object.keys(data).length !== 0) {
console.log("Poll", data)
return;
} else {
setTimeout(poll, 5000, ++count);
}
}
catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
setTimeout(poll, 5000, 1);
}
}
async function my_plot() {
console.log("my plot")
}
Code output:
Polling 1
my plot
Polling 2
Polling 3
Poll [1,2,3]
Expected:
Polling 1
Polling 2
Polling 3
Poll [1,2,3]
my plot
Don't use setTimeout directly from within an async function. Instead, use a Promise-based wrapper.
It's surprising that modern ECMAScript doesn't come with an in-box Promise-based version of setTimeout, but it's straightforward to implement:
function delay( timeout ) {
if( typeof timeout !== 'number' || timeout < 0 ) throw new Error( "Timeout must be a non-negative integer milliseconds delay value." );
return new Promise( function( resolve ) {
setTimeout( resolve, timeout );
});
}
Then you can rewrite your poll function with a "real" while loop, like so (below).
I think your poll function should return a true/false value to indicate success or failure to the caller, if you ever need to.
Consider using typeof instead of less safe checks like Object.keys(data).length - or at least using a typeof check before using Object.keys.
Though annoyingly typeof null === 'object', so you will always need a !== null check, grumble...
As an alternative, consider having your own type-guard function (yes, I know this isn't TypeScript), that way you get even stronger guarantees that data contains what you need (as JS does not have static type checking).
async function poll( count = 1 ) {
console.log(`Polling ${count}`);
let i = 0;
do {
try {
const data = await getData();
if( isMyData( data ) ) {
return true;
}
}
catch( err ) {
console.error( err );
}
console.log( "Polling again in 5 seconds." );
await delay( 5000 );
i++;
}
while( i < count );
console.log( `Gave up after ${count} attempts.` );
return false;
}
// Type-guard:
function isMyData( data ) {
return (
( typeof data === 'object' )
&&
( data !== null )
&&
( 'this is my object' in data )
&&
( data['there are many like it but this one is mine'] )
&&
( data.myJavaScriptEngineIsMyBestFriend )
&&
data.itIsMyLife
&&
data.withoutMe_javaScriptIsUseless
&&
data.withoutJavaScript_iAmUseLess > 0
);
}
Note that if you intend to catch errors thrown by getData you should use a minimally scoped try instead of having more logic in there, as generally you won't want to catch unrelated errors.
Using the answer from How to make a promise from setTimeout, you can use a traditional loop.
function later(delay, value) {
return new Promise(resolve => setTimeout(resolve, delay, value));
}
async function poll() {
for (let count = 1;; count++) {
console.log(`Polling ${count}`);
try {
const data = Math.random(); // simulated data
if (data < 0.2) { // simulated 20% success rate
console.log("Poll", data)
return data;
} else {
console.log("Retrying in 5 seconds");
await later(5000);
}
} catch (err) {
console.log(`${err}. Polling again in 5 seconds.`);
count = 1;
await later(5000);
}
}
}
async function main() {
console.log("Start");
await poll();
console.log("Poll done");
}
main();
Related
Struggling to setup this function using async and await.
I have a function which maps over an object array. For each object, we make some external axios requests, receive a response before outputting an update object. I need to map over each object, update and return an array of updated objects.
Problem I am having, is that the output is not waiting for the resolved promise, resulting in undefined values being pushed to my array.
I note the array is also being logged before we handle each of the objects.
The following two functions are in question:
To build the object:
const createHRObj = async function (workflow) {
if (workflow.type_of_leave === "Personal") {
getAbsenceDetail(
`${workflow.employeeID}`,
`${workflow.startDate}`,
`${workflow.endDate}`
).then((res) => {
try {
console.log(`------- Handling Personal Leave! ----------`);
console.log(workflow);
if (res.Status != 0)
throw new Error(`PeopleHR absence status code: ${res.Status}`);
let absences = res.Result;
if (absences.length === 0) workflow.on_PeopleHR = false;
if (absences.length > 0) {
let count = 0;
absences.forEach((absence) => {
if (
Date.parse(absence.StartDate) <=
Date.parse(workflow.startDate) &&
Date.parse(absence.EndDate) >= Date.parse(workflow.endDate)
)
count++;
});
count > 0
? (workflow.on_PeopleHR = true)
: (workflow.on_PeopleHR = false);
}
console.log(`------- Absence Workflow Output! ----------`);
console.log(workflow);
console.log(
`------- Updating and returning workflow! ----------`
);
return workflow;
// console.log(`------- output from checkedarray ----------`);
// console.log(checkedArray)
} catch (err) {
console.log(err);
}
});
// Else, check for holiday
} else {
getHolidayDetail(
`${workflow.employeeID}`,
`${workflow.startDate}`,
`${workflow.endDate}`
).then((res) => {
try {
console.log(`------- Handling Business Leave! ----------`);
console.log(workflow);
if (res.data.Status != 10)
throw new Error(
`PeopleHR holiday status code: ${res.data.Status}`
);
let holidays = res.data.Result;
if (holidays.length === 0) workflow.on_PeopleHR = false;
if (holidays.length > 0) {
let count = 0;
holidays.forEach((holiday) => {
if (
Date.parse(holiday.StartDate) <=
Date.parse(workflow.startDate) &&
Date.parse(holiday.EndDate) >= Date.parse(workflow.endDate)
)
count++;
});
count > 0
? (workflow.on_PeopleHR = true)
: (workflow.on_PeopleHR = false);
}
console.log(`------- Absence Workflow Output! ----------`);
console.log(workflow);
console.log(
`------- Updating and returning workflow! ----------`
);
return workflow;
// console.log(`------- output from checkedarray ----------`);
// console.log(checkedArray)
} catch (err) {
console.log(err);
}
});
}
};
The second function, which calls the above function to handle the object creation is as follows:
const peopleHR_Check = async function (workflows) {
console.log(`----- We're now inside the PeopleHR Checker -------`);
Promise.all(
workflows.map(async (workflow) => {
let outputObj = await createHRObj(workflow)
return outputObj
})
)
.then(res => console.log(res))
};
You need to return the Promise from the async function createHRObj:
return getAbsenceDetail(...).then(...)
However, it is simpler to use await since it is an async function for more linear flow.
const res = await getAbsenceDetail(...);
// use res (move the code in .then() here)
// return the final output at the end
function fakeRequest(url) {
return new Promise((resolve, reject) => {
delay = Math.floor(Math.random() * 4500) + 500;
setTimeout(() => {
if (delay > 4500) {
resolve(url + ": success")
}
else {
reject(url = ": error")
}
}, delay);
})
})
}
async function makeTwoRequests() {
let data1 = await fakeRequest("/page1");
console.log("Data 1:", data1)
let data2 = await fakeRequest("/page2");
console.log("Data 2:", data2)
}
makeTwoRequests()
When I remove data1 or data2 and just have 1 await it works. But when there are 2 or more it errors out saying: Uncaught (in promise) : error
I don't know what's happening here. Please help me out
Thanks!
If you await a promise that rejects, it becomes a thrown error.
Change the reject inside fakeRequest to a resolve and I bet the problem goes away.
This works for me:
function fakeRequest(url) {
return new Promise((resolve, reject) => {
let delay = Math.floor(Math.random() * 4500) + 500;
setTimeout(() => {
if (delay > 4500) {
resolve(url + ": success")
} else {
resolve(url + ": error")
}
}, delay);
})
}
async function makeTwoRequests() {
let data1 = await fakeRequest("/page1");
console.log("Data 1:", data1)
let data2 = await fakeRequest("/page2");
console.log("Data 2:", data2)
}
makeTwoRequests()
There were a couple other issues with the sample code:
delay ought to be declared with a keyword like let, const, or var; this is necessary to guarantee that each invocation of the function uses its own private version instead of a shared one, and to prevent this function from changing variables in the outer scope
the reject call uses = instead of +, which redefines and returns the url variable instead of concatenating the URL string with the literal : error string
there was an extra closing }), which prevented this code from parsing
But I think I get what you're up to: you're trying to simulate a situation in which API calls usually succeed, except for some that fail by timing out.
Your fakeRequest does accomplish that, but your calling code isn't capable of handling the problem. For that, you'd need something like this:
async function makeTwoRequests() {
let data1
try {
data1 = await fakeRequest("/page1");
} catch ( error ) {
console.error(`request 1 threw`, error)
// here, you might return, or re-throw, or fall through
}
let data2
try {
data2 = await fakeRequest("/page2");
} catch ( error ) {
console.error(`request 2 threw`, error)
// here, you might return, or re-throw, or fall through
}
}
I have a recursive async function getResponse(url,attempts = 0), which polls external api for the response and resolves or exits after reaching X number of retries or on a server error.
However, it's internal "clock" is based off the number of retries (after allowing for delays to avoid rate limits), but I also want to have a flexibility in setting a time based timer, which would resolve the function and end the recursion. Ideally, I want to be able to wrap time based timer around my recursive async function, like so timed(getResponse(url),3400)
I have only managed to have both the time based and "retries" based timer working together, by packaging both timers in one async function with local variable expired serving as an exit flag and setting Promise.race conditions on both functions.
async function timedgetResponse (expiry = 3500,url) {
let expired = false;
async function timeout(expiry){
await new Promise(_=> setTimeout(_,expiry));
expired = true;
return false;
};
async function getResponse(url,attempts = 0){
try {
if(expired){ return false; };
const limit = 10;
if(attempts >= limit){ok: false, e:"MAX_ATTEMPTS"};
const rawRes = await fetch(url,
{
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json'
}
});
if (!rawRes.ok) { throw (Error('SERVER_ERROR')); };
const res = await rawRes.json();
if(!res || res.status === 0){ throw (Error(res.request)); };
return {ok: true, res: res.request};
} catch(e){
const err = e.message;
if(err === "RESPONSE_NOT_READY"){
await new Promise(_ => setTimeout(_, 333));
attempts +=1;
return getResponse(url,attempts);
} else
if(err === "SERVER_ERROR_ON_RESOLVER"){
await new Promise(_ => setTimeout(_, 10000));
attempts +=1;
return getResponse(url,attempts);
} else {
return {ok: false, e:"MISC_ERROR"};
};
};
};
const awaited = await Promise.race([
getResponse(url),
timeout(expiry)
]);
return awaited;
};
I sense that it is not a correct way to do it and would appreciate any help towards timed(getResponse(url),3400) solution.
I have a function that might meet your need. I have updated it based on how I have interpreted your needs. The idea is you will poll until something is true ie resolves or you exceed the max attempts. It has a built-in configurable delay.
The idea here is you'd pass in a function that wraps your fetch call which would eventually resolve/reject.
setPolling(pollFunc, freq = 1000, maxAttempts = 3)
pollFunc = function that takes no args and returns a promise that eventually resolves or rejects.
freq = how frequently to run pollFunc in milliseconds
maxAttempts = max attempts before giving up
const setPolling = async (pollFunc, freq = 1000, maxAttempts = 3, _attempts = 1) => {
const wait = (delay) => new Promise(resolve=>setTimeout(resolve, delay))
try {
return await pollFunc()
} catch (e) {
if (_attempts < maxAttempts) {
await wait(freq)
return await setPolling(pollFunc, freq, maxAttempts, ++_attempts)
}
throw (e instanceof Error) ? e : new Error((typeof e !== 'undefined') ? e : 'setPolling maxAttempts exceeded!')
}
}
async function alwaysFail() {
throw new Error(`alwaysFail, failed because that's what it does!`)
}
function passAfter(x) {
let i = 0
return async ()=> {
if (i > x) return `passAfter succeeded because i(${i}) > x(${x})`
throw new Error(`i(${i++}) < x(${x})`)
}
}
setPolling(alwaysFail)
.catch((e)=>console.error(`alwaysFail, failed!\n${e.message}\n${e.stack}`))
setPolling(passAfter(5), 500, 10)
.then((res)=>console.log(`passAfter, succeeded!\n${res}`))
.catch((e)=>console.error(`passAfter, failed!\n${e.message}\n${e.stack}`))
On the basis that you want to stop retrying when a timer expires, then you can employ a token to convey a stop signal to the recursive process.
Something like this should do it:
const poll = async (work, options, token) => {
const settings = Object.assign({ 'delay':0, 'attempts':1, 'maxAttempts':3 }, options);
// Utility function which returns a Promise-wrapped setTimeout
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Two mechanisms for stopping the recursion.
// Performing the tests here ensures they are applied before the first work() call.
// 1. token-borne stop signal
if(token.stop) {
throw new Error('poll(): stopped');
}
// 2. max attempts reached
if (settings.attempts >= settings.maxAttempts) {
throw new Error('poll(): max attempts reached');
}
// Do the work, and recurse on error
try {
return await work();
}
catch (e) {
await delay(settings.delay);
settings.attempts += 1; // Mutate/pass `settings`; the original `options` is not guaranteed to have an `attempts` property.
return await poll(work, settings, token);
}
}
Call as follows:
// token
const token = {}; // or {'stop':false} if you like
// Time based timer:
setTimeout(() => {
token.stop = true; // raise the 'stop' flag
}, 60000); // or whatever
let pollPromise = poll(doSomethingAsync, {'delay':1000, 'maxAttempts':100}, token)
.then((res) => console.log(res))
.catch((e) => console.error(e));
Note that on setting the stop signal:
a successful reponse from in-flight work will still come through.
further recursion will be prevented but no attempt is made to abort the in-flight work.
With a little more thought these behaviours could be changed depending on exactly what is required.
I've been using promises + async/await for some time now, I thought I was comfortable with them, but this situation really has me stumped. Any advice/guidance is greatly appreciated!
Context: My app is making URI requests, but I have to delay each request by a couple seconds. To make the delay more exact, I set up a queue for making the requests. No problem using callbacks, but I've been at it for a while, and I can't seem to wrap my head around how to pull it off using promises.
Sandboxed the callback code below:
const queue = []
let t;
function addToQueue(params, cb) {
queue.push({params,cb})
_run()
}
function _run() {
if (!t && queue.length) {
const {params,cb} = queue.shift()
_dummyFetch(params).then( data => cb(data) )
_startTimer()
}
}
function _startTimer() {
t = setTimeout( _endTimer, 2000 )
}
function _endTimer() {
t = null
_run()
}
async function _dummyFetch() {}
Sandbox debug:
function seconds() { return Math.round(new Date().getTime()/1000) }
function log(t) { console.log(t + " " + seconds()) }
function logFn(t) { return () => log(t) }
log("init")
addToQueue({}, logFn("request 1")) // should be close/same as init time
addToQueue({}, logFn("request 2"))
addToQueue({}, logFn("request 3"))
// If I could figure out how to make it a promise:
// addToQueue( ... ).then( data => ... )
Create a new Promise in the addToQueue function and put the resolver function on the queue. Then later resolve the promise with the fetch result:
function addToQueue(params) {
return new Promise(resolve => {
queue.push({params, resolve})
_run()
})
}
function _run() {
if (!t && queue.length) {
const {params, resolve} = queue.shift()
resolve(_dummyFetch(params))
_startTimer()
}
}
Alternatively, you can promisify the entire queue code, and use a promise as the queue itself.
const queue = Promise.resolve();
function addToQueue(params) {
const result = queue.then(() => _dummyFetch(params));
queue = queue.then(timer);
return result;
}
function timer() {
return new Promise(resolve => {
setTimeout(resolve, 2000);
});
}
async function _dummyFetch() {}
You could even make the queue wait for the fetch if it takes longer than 2s, so that two requests never run concurrently, by simply changing
queue = Promise.all([result, queue.then(timer)]).then(res => void res, err => void err);
I have a standard function that successfully checks to see if a particular app is installed. But that function takes a few moments to execute and I need to bind it into a promise.then(function (response) { ...} because the app checker takes too long to execute...causing an async issue with the intended response from the app check function. But I can't get it work.
checkSocialApp only returns a true or false:
function checkSocialApp(objApp) {
if (device.platform == "iOS") {
var scheme = objApp.ios;
} else {
var scheme = objApp.and;
}
if (objApp.appName == "Facebook") {
return true ; // FB doesn't need app, can be logged in via browser
} else {
return appAvailability.check(
scheme,
function() { // success callback
console.log(scheme + " is Installed") ;
return true ;
}, function () { // Error callback
console.log(scheme + " is NOT Installed") ;
alert("You do not have the " +objApp.appName+ " app installed.") ;
return false ;
}
);
}
checkSocialApp(appObj).then(function(response){ // errors on 'then'
if (response == true) { // app IS installed
console.log("CheckApp True") ;
appObj.loginApp() ;
} else if (response == false) { // app IS NOT installed
console.log("CheckApp False") ;
appObj.disableApp() ;
}
}) ;
The above errors out on the .then. So I try to bind it to a promise.resolve:
var promise1 = Promise.resolve(checkSocialApp(appObj)) ;
promise1.then(function(response) {
if (response == true) ...
This executes the checkSocialApp function successfully (as I see proper console messages printing from within that function), but I am not getting the response back into the remaining part of the .then for processing.
You have to do something like this return a promise in your function:
function checkSocialApp(objApp) {
return new Promise( function(resolve)={
if (device.platform == "iOS") {
var scheme = objApp.ios;
} else {
var scheme = objApp.and;
}
if (objApp.appName == "Facebook") {
resolve (true) ; // FB doesn't need app, can be logged in via browser
} else {
return appAvailability.check(
scheme,
function() { // success callback
console.log(scheme + " is Installed") ;
resolve (true) ;
}, function () { // Error callback
console.log(scheme + " is NOT Installed") ;
alert("You do not have the " +objApp.appName+ " app installed.") ;
resolve (false) ;
}
);
}
})
}
checkSocialApp(appObj).then(function(response){ // errors on 'then'
if (response == true) { // app IS installed
console.log("CheckApp True") ;
appObj.loginApp() ;
} else if (response == false) { // app IS NOT installed
console.log("CheckApp False") ;
appObj.disableApp() ;
}
}) ;
Does checkSocialApp usually take a callback? You can wrap it in a promise like this:
function checkSocialAppPromise ( appObj ) {
return new Promise( function ( resolve ) {
checkSocialApp( appObj, resolve )
});
}
What you have should work, strictly speaking. If checkSocialApp(appObject) returns true or false, then you should get it. What you show works. If it isn't, then there must be something odd going on.
const someFunc = () => 5;
Promise.resolve(someFunc()).then(result => console.log(result));
However, it probably won't do what you are trying to do. You can't magically make a long-running synchronous function run async.
For example, if I have this function:
function runSlow() {
const start = Date.now();
while (Date.now() - start < 5000) { } // force a loop to wait 5 seconds
return true;
}
runSlow();
console.log('done');
Which takes 5 seconds to complete. I can't just wrap it up and make it asynchrnous suddenly:
function runSlow() {
const start = Date.now();
while (Date.now() - start < 5000) { } // force a loop to wait 5 seconds
return true;
}
Promise.resolve(runSlow())
.then(response => console.log(response));
While technically it is wrapped in a promise, it still takes 5 seconds to run and it still blocks things (try clicking while it's running and you'll see your browser is unresponsive).
If you want it to run properly, you'll have to modify the function itself to stop being synchronous all together. There isn't any way to just wrap it up in it's own thread or anything.