I have a button when user click on it I will send a request and receive answer. If user click 100 times on this button I want to send 100 requests to server and each request send after previous. because I need previous response in next request.
example:
<button #click="sendRequest">send</button>
methods:{
sendRequest:function(){
axios.post('https:/url/store-project-item', {
'id': this.project.id,
"items": this.lists,
'labels': this.labels,
'last_update_key': this.lastUpdateKey,
'debug': 'hYjis6kwW',
}).then((r) => {
if (r.data.status) {
this.change = false
this.lastUpdateKey = r.data.lastUpdateKey;
this.showAlert('success')
} else {
if (r.data.state == "refresh") {
this.showAlert('error')
this.getProject()
} else {
this.showAlert('error')
}
}
}).catch(() => {
this.showAlert('error')
})
}}
I keep a higher-order function (i.e. a function that returns a function) withMaxDOP (DOP = degrees-of-parallelism) handy for this kind of thing:
const withMaxDOP = (f, maxDop) => {
const [push, pop] = createAsyncStack();
for (let x = 0; x < maxDop; ++x) {
push({});
}
return async(...args) => {
const token = await pop();
try {
return await f(...args);
} finally {
push(token);
}
};
};
The function makes use of an async stack data structure (implementation is in the attached demo), where the pop function is async and will only resolve when an item is available to be consumed. maxDop tokens are placed in the stack. Before invoking the supplied function, a token is popped from the stack, sometimes waiting if no token is immediately available. When the supplied completes, the token is returned to the stack. This has the effect of limiting concurrent calls to the supplied function to the number of tokens that are placed in the stack.
You can use the function to wrap a promise-returning (i.e. async) function and use it to limit re-entrancy into that function.
In your case, it could be used as follows:
sendRequest: withMaxDOP(async function(){ /*await axios.post...*/ }, 1)
to ensure that no call to this function ever overlaps another.
Demo:
const createAsyncStack = () => {
const stack = [];
const waitingConsumers = [];
const push = (v) => {
if (waitingConsumers.length > 0) {
const resolver = waitingConsumers.shift();
if (resolver) {
resolver(v);
}
} else {
stack.push(v);
}
};
const pop = () => {
if (stack.length > 0) {
const queueItem = stack.pop();
return typeof queueItem !== 'undefined' ?
Promise.resolve(queueItem) :
Promise.reject(Error('unexpected'));
} else {
return new Promise((resolve) => waitingConsumers.push(resolve));
}
};
return [push, pop];
};
const withMaxDOP = (f, maxDop) => {
const [push, pop] = createAsyncStack();
for (let x = 0; x < maxDop; ++x) {
push({});
}
return async(...args) => {
const token = await pop();
try {
return await f(...args);
} finally {
push(token);
}
};
};
// example usage
const delay = (duration) => {
return new Promise((resolve) => setTimeout(() => resolve(), duration));
};
async function doSomething(name) {
console.log("starting");
// simulate async IO
await delay(1000);
const ret = `hello ${name}`;
console.log(`returning: ${ret}`);
return ret;
}
const limitedDoSomething = withMaxDOP(doSomething, 1);
//call limitedDoSomething 5 times
const promises = [...new Array(5)].map((_, i) => limitedDoSomething(`person${i}`));
//collect the resolved values and log
Promise.all(promises).then(v => console.log(v));
Related
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;
};
Here is my code:
let delayTimeout = null;
const delayExecution = mls => {
console.log('Delaying for', mls);
return new Promise(resolve => {
delayTimeout = setTimeout(() => resolve('ok'), mls);
})
}
const main = async () => {
axios.post('URL', {data})
.then(response => {
if(response passes some condition){
clearTimeout(delayTimeout);
}
})
const res = await delayExecution(30000)
console.log("DONE!")
}
main();
After the axios call, I may want to terminate the delayExecution by clearing the timeout inside it. How do I clearTimeout inside my delayExecution function but still resolve the Promise?
In essence, I'm trying to finish delayExecution before its time, but still resolve the promise inside it.
Based on your edit, I'll just leave another response. Note that I haven't tested it, my mind is currently focused on my code I'm writing alongside this hehe
let delayTimeout = null;
let resolveHandler = null;
const delayExecution = mls => {
console.log('Delaying for', mls);
return new Promise(resolve => {
resolveHandler = resolve;
delayTimeout = setTimeout(() => resolve('ok'), mls);
})
}
const main = async () => {
axios.post('URL', {data})
.then(response => {
if(response passes some condition){
resolveHandler('ok');
clearTimeout(delayTimeout);
}
})
const res = await delayExecution(30000)
console.log("DONE!")
}
main();
The idea is just to assign the resolve function to another auxiliary variable which you can then use elsewhere :)
doneFunc should have the clearTimeout within it, so after the function is complete the timeout is cleared.
Also, for the first setTimeout parameter, you can just pass the name of the function.
Actually for timeout, you don't need the clearTimeout since it will only be ran ONCE compared to interval which is continuing run.
const doneFunc = () => {console.log('Finished job');clearTimeout(f);}
const f = setTimeout(doneFunc, 100);
If you want to run the function independently from the timeout, just declare the function outside of it, then call it whenever you want. You have most of the code done
const doneFunc = () => console.log('Finished job');
const f = setTimeout(() => doneFunc(), 10000);
/* Seome logic here */
if (condition to run before timeout) {
clearTimeout(f);
doneFunc();
}
/* end of logic */
I have imagined that :
const runOnDelay = function( fct, delay )
{
let obj = {}
, isDone = false
, refTim = setTimeout(()=>
{
isDone = true
fct()
}, delay)
;
obj.stop = () =>
{
clearTimeout(refTim)
if (!isDone)
fct()
isDone = true
}
return obj
}
usage:
const doneFunc = () => console.log('Finished job')
let myBoy = runOnDelay(doneFunc, 1000)
//...
myBoy.stop()
Am using sequilizer and struggling because the method is forever in pending state.
The following is a simplified version of what I am trying to do. Basically, an API makes use of the below methods, by calling BatchProcessor, which was supposed to process the provided json.
I basically want BatchProcessor to get themeprice and themegate from FinalTheme method but the promise is forever pending.
export default {
async FinalTheme(id) {
return db.Themes.findOne({
where: {
ID: id
},
attributes: ["ThemeCost","ThemeGate"],
limit: 1
})
.then(data => {
if (data == null) {
return -1;
}
return {
cost: data["ThemeCost"],
gate: data["ThemeGate"]
};
})
.catch(err => {
return false;
});
},
async BatchProcessor(record, index_number) {
const SQL ="SELECT * FROM themes";
return db.sequelize
.query(SQL, {
type: db.sequelize.QueryTypes.SELECT
})
.then(themes => {
// do we have data here?
const totalThemes = themes.length;
let lastAmount = record["Amount"];
for (
let counter = 0;
counter < totalThemes - 1;
counter++
) {
const CustomerFinalTheme = this.FinalTheme(record["CustomerID"]); // FOREVER PENDING
}
})
.catch(err => {
console.log(JSON.stringify(err));
});
},
};
What am I doing wrong exaclty?
this.FinalTheme(... returns a promise and not the value you have to do:
this.FinalTheme(record["CustomerId"]) // where is the record assigned?
.then(data => {
const CustomerFinalTheme = data;
})
also no need to use async when declaring the functions ie the following is ok:
FinalTheme(id) {
return db.Themes.findOne({
[...]
}
You are running a loop inside then block of BatchProcessor. you can await inside for loop.
async BatchProcessor(record, index_number) {
const SQL ="SELECT * FROM themes";
const themes = await db.sequelize.query(SQL, { type: db.sequelize.QueryTypes.SELECT });
const totalThemes = themes.length;
let lastAmount = record["Amount"];
for (let counter = 0; counter < totalThemes - 1; counter++) {
const CustomerFinalTheme = await this.FinalTheme(record["CustomerID"]);
}
return 'ALL DONE';
}
How to insert data into db with index as the title? I think this is typical async question but I can't solve it. The order of i been inserted not in sequence.
const { times } = require('lodash')
module.exports = (async () => {
try {
const createJob = async (i) => {
console.log(i) //executed first 1 - 50 first
const { data } = await axios
.post('http://localhost:3000/job/create', {
"title": i,
"created_at": Date.now()
})
if(data) {
console.log('job created. ', data)
}
}
times(50, (i) => {
createJob(++i)
});
} catch(e) {
console.log('Error creating ad. ', e)
}
})()
You can chain your promises so each createJob is called after the previous one is finished. You can create an array of indexes and use array.reduce to do the chaining. In the code below, I replace your axios call with a new Promise(...), just for simulating:
var createJob = async (i) => {
console.log(i) //executed first 1 - 50 first
const { data } = await new Promise((resolve, reject) => setTimeout(() => resolve({ data: i}), 1000));
if (data) {
console.log('job created. ', data);
}
return data;
}
var arr = Array.from(new Array(10), (e, i) => i);
arr.reduce((m, o) => m.then(() => createJob(o)), Promise.resolve());
You can also use Promise.all to solve this. Essentially you save all the promises of the request in an array. Once, all the requests are complete, you can iterate over them.
const axios = require('axios');
module.exports = (async () => {
try {
const createJob = async (i, url) => {
console.log(i, url) //executed first 1 - 50 first
return axios.get(url);
}
const a = ['http://httpbin.org/anything/123', 'http://httpbin.org/anything/456']
const promiseArray = [];
for (let j = 0; j < 4; j++) {
promiseArray.push(createJob(j, a[j % 2]));
}
Promise.all(promiseArray).then((result) => {
console.log('result', typeof (result));
for (let i = 0; i < result.length; i++) {
console.log(result[i].data.url);
}
});
} catch (e) {
console.log('Error creating ad. ', e)
}
})()
I have used httpbin for making actual calls and random payload to make sure the execution order is always the same.