Javascript promise chaining with promise.all not working? - javascript

I have the following code. When i run it i get this error " Cannot read property 'then' of undefined" at the first line in my code when calling getQueryToShift. It seems like getQueryToShift is not working like I intended. What is the correct way to use Promise.all so that the original promise I am declaring on the first line waits for all the promises in the promises array declared inside the getQueryToShift function to resolve before executing what is inside the then block ?
promise = getQueryToShift(hourDiff, options, map, baseBuildStart, oldFrom).then(values => { // error is here
// wait for promise before handling data in map passed as parameter
});
function getQueryToShift(hourDiff, options, map, baseBuildStart, oldFrom) {
let promises = [];
datasourceSrv.get(options.targets[0].datasource).then(ds => {
for (let i = 0; i < daysDiff - 1; i++) {
options.range.from._d = dateToMoment(oldFrom, false).add(i, 'h').toDate();
options.range.to._d = dateToMoment(options.range.from._d, false).add(1, 'h').toDate();
ds.query(options).then(result => {
promises.push(createQueryPromise(map, baseBuildStart, result.data));
});
}
return Promise.all(promises);
});
}
function createQueryPromise(map, baseBuildStart, data) {
return new Promise((resolve) => {
data.forEach(datum => {
//parsing data and adding it to map passed in as parameter
})
resolve();
});
}

The question is incomplete because createQueryPromise() function makes no sense by itself. It does not show an asynchronous operation at all, thus without one there is no need for a promise. And, if there is an asynchronous operation inside your .forEach() look, then you will need to coordinate that async operation, but you don't show the code for that so we can't help you fix that and we need to understand what that function is actually doing before we can help with a full and correct implementation of getQueryToShift().
On top of that, here are a couple other things that need fixing:
You aren't returning your promise from getQueryToShift(). Change this:
datasourceSrv.get(options.targets[0].datasource).then(ds => {
to this:
return datasourceSrv.get(options.targets[0].datasource).then(ds => {
And, you are ignoring the promise returned by:
ds.query(options).then(...)
Thus, your code wouldn't wait for that operation to finish. This is probably the promise that you need to collect in the array and use Promise.all() with, not the one you are doing it with.

Your code does the Promise.all on an empty array - you need to return the promise:
function* getDays(daysDiff, oldFrom) {
for (let i = 0; i < daysDiff - 1; i++) {
var options = {};
options.range.from._d = dateToMoment(oldFrom, false).add(i, 'h').toDate();
options.range.to._d = dateToMoment(options.range.from._d, false).add(1, 'h').toDate();
yield options;
}
}
function getQueryToShift(hourDiff, options, map, baseBuildStart, oldFrom) {
// return here
return datasourceSrv.get(options.targets[0].datasource).then(ds => {
return Promise.all(
Array.from(getDays(daysDiff, oldFrom), option => db.query(option))
);
});
}
Although I warmly recommend using an async function instead:
async function getQueryToShift(hourDiff, options, map, baseBuildStart, oldFrom) {
const ds = await datasourceSrv.get(options.targets[0].datasource);
await Promise.all(Array.from(getDays(daysDiff, oldFrom), db.query));
}

Related

javascript - not able to execute the promise from array one by one

I found that I cannot resolve the promises one by one.
This is my code
const promises = mkt.map((marketItem) => {
return context.program.account.chain
.fetch(marketItem[0].instrument)
.then((instrumentRes) => {
return Test(context, marketItem[0]).then(
testResult => {
return Promise.all([
functionA(),
functionB(),
]).then((result) => {
return result
});
}
);
});
});
console.log(promises)
for (let i=0; i < promises.length; i++) {
const val = await promises[i]();
console.log(val);
}
error
promises[i]() is not a function
Why is that?How can I solve it?
promises[i]() is not a function
Correct, this is because promises is an array of Promise objects, not functions.
You can dump this promises array into Promise.all and wait for them all to resolve.
Promise.all(promises)
.then(resolvedPromises => {
// handle array of resolved promises
});
but I am working on some real time update stuff. If I use Promise.all
it will throw error Too many request, so I want to do it one by one to
see the effect
For this then I think you want to iterate on the mkt array and wait for each created Promise to resolve. Refactor the mapping callback into a standalone function that you can manually invoke within the for-loop, awaiting the the Promise to settle.
const request = (marketItem) => {
return context.program.account.chain
.fetch(marketItem[0].instrument)
.then((instrumentRes) => Test(context, marketItem[0]))
.then(testResult => Promise.all([functionA(), functionB()]))
.then((result) => result);
}
for (let i=0; i < mkt.length; i++) {
try {
const val = await request(mkt[i]);
console.log(val);
} catch(error) {
// handle any error
}
}
Just removing the () from the await call should already work.
const val = await promises[i];
However keep in mind that all the promises have already been "started" (I don't know a better word for that) at that point and you're just retrieving the results or an error with the await call.

How to wait until multiple files are processed before calling finished function js

The following function runs after a drag and drop operation of multiple files.
function getFilesInfo(ev){
for (let i = 0; i < ev.dataTransfer.items.length; i++) {
if (ev.dataTransfer.items[i].kind === 'file') {
let file = ev.dataTransfer.items[i].getAsFile();
//getFileInfo adds string to DOM element
//Note the promise usage ...
file.arrayBuffer().then((data)=>getFileInfo(file.name,data));
}
}
}
I can't figure out how to call a function after all of the promises in this function finish.
Basically I want something like this, sequentially:
getFilesInfo(ev);
//getFileInfo(<file1>);
//getFileInfo(<file2>);
//getFileInfo(<file3>);
// etc.
//run only after all getFileInfo() calls have finished
processResults();
The tricky part is that reading the files generates a promise for each file that gets called when the file has been read into memory (part of the arrayBuffer() call). I can't figure out how to delay processResults because getFilesInfo finishes after all of the read calls have been triggered, not (from what I can tell), after the getFileInfo functions have finished.
It seems like perhaps I could somehow add all arrayBuffer calls to an array and then do some promise chaining (maybe?) but that seems awkward and I'm not even sure how I would do that.
You can use Promise.all to wait for an array of promise to finish:
async function getFilesInfo(ev) {
// create list of jobs
const jobs = [];
for (const item of ev.dataTransfer.items) {
if (item.kind === 'file') {
let file = item.getAsFile();
jobs.push(file.arrayBuffer().then(data => {
getFileInfo(file.name, data);
}));
}
}
// wait for all promise to fullfil
await Promise.all(jobs);
}
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You could do it that way:
function getFilesInfo(ev){
return ev.dataTransfer.items.filter(item=>item.kind === 'file').map(item=>{
let file = item.getAsFile();
return file.arrayBuffer().then((data)=>getFileInfo(file.name,data));
});
}
Promise.all(...getFilesInfo(ev)).then(_=>{
processResults();
});
// or with async/await
(async ()=>{
await Promise.all(...getFilesInfo(ev));
processResults();
})()
async function getFilesInfo(ev) {
await Promise.all(ev.dataTransfer.items.map(async (i) => {
const file = i.getAsFile();
const data = await file.arrayBuffer();
return getFileInfo(file.name, data);
}));
}
await getFilesInfo(ev); // will be awaited until all the promises are resolved
processResults();
Let me know if that helps.
The conceptual hurdle I was running into was that I was thinking of the then function as returning the results, not promises. Also, many of the examples I've seen with Promise.all are usually just concatenating explicit calls, not building an array in a loop.
As suggested by Bergi, I simply added the calls to an array, and then passed that array into Promise.all
function getFilesInfo(ev) {
// create list of jobs
let jobs = [];
for (const item of ev.dataTransfer.items) {
if (item.kind === 'file') {
let file = item.getAsFile();
jobs.push(file.arrayBuffer().then(data => {
getFileInfo(file.name, data);
}));
}
}
return jobs;
}
//The call in the parent
let jobs = getFilesInfo(ev);
Promise.all(jobs).then(processResults);

How to avoid using two loops when using Promise.all() dynamically?

Whenever you see Promise.all() being used, it is usually used with two loops, how can I make it into only one without using async and mantaining execution order?
The question is not about the order, I know promise.all preserves order, the question is about how to avoid two loops when you just need the returned value
function timeout(x){
return new Promise( resolve => {
setTimeout( () => {
return resolve(x);
},x)
})
}
const promises = [];
const results = [];
//First loop, array creation
for (i = 0; i < 20; i++) {
const promise = timeout(i*100)
promises.push(promise);
}
Promise.all(promises).then( resolvedP => {
//Second loop, async result handling
resolvedP.forEach( (timeout,i) => {
results.push({
index : i,
timeout : timeout
})
})
console.log(results);
})
//End of code
Now, this can be solved with async but in my context I can't use it, for example :
//Only one loop
for (i = 0; i < 20; i++) {
const timeoutAwait = await timeout(i*100);
results.push({
index : i,
timeout : timeoutAwait
})
}
console.log(results)
//End of code
What I have tried is the following, but the promise doesn't return the resolve value without using .then() :
for (i = 0; i < 20; i++) {
const promise = timeout(i*100)
promises.push(promise);
results.push({index : i, timeout : promise});
}
Promise.all(promises).then( resolvedP => {
resolvedP.forEach( (timeout,i) => {
results.push({
index : i,
timeout : timeout
})
})
console.log(results);
//results[0].timeout is a Promise object instead of 0
})
//End of code
So, is there any way I can make my first code sample in only one loop? Please ignore the context, is only an example.
function timeout(x) {
return new Promise(resolve => {
setTimeout(() => {
return resolve(x);
}, x);
});
}
const promises = [];
const results = [];
//First loop, array creation
for (let i = 0; i < 20; i++) {
const promise = timeout(i * 100).then(x => results.push({
index: i,
timeout: x
}));
promises.push(promise);
}
Promise.all(promises).then(() => {
console.log(results);
});
If you want to preserve execution/results order assign results using i index instead of .push
const promise = timeout(i * 100).then(x => results[i] = {
index: i,
timeout: x
});
As by the Promise.all documentation, the order will be preserved. It says about the return value:
A pending Promise in all other cases. This returned promise is then
resolved/rejected asynchronously (as soon as the stack is empty) when
all the promises in the given iterable have resolved, or if any of the
promises reject. See the example about "Asynchronicity or
synchronicity of Promise.all" below. Returned values will be in order
of the Promises passed, regardless of completion order.
(Highlighted by me.)
I find it more easy to understand if you map over an array of values instead of looping:
const timeout = x=>Promise.resolve(x);
Promise.all(
[...new Array(3).keys()]//array [0,1,2]
.map(x=>timeout(x*100))//[timeout(0*100),timeout(1*100),...
).then(
result=>result.map(//results of the timeout, map to object
(timeout,index)=>({
index,
timeout
})
)
).then(
result=>console.log('result:',result)
)
It's usually not a good idea to asynchronously mutate a shared variable (the results array). Just have the promise resolve and create the result you need in the last .then, now your promise resolves to the value you want.

Using Async / Awaits within promise

I have a certain promise chain in my code that looks like this:
myPromise()
.then(getStuffFromDb)
.then(manipulateResultSet)
.then(manipulateWithAsync)
.then(returnStuffToCaller)
Now, in my manipulateWithAsync I'm trying to enhance my result set by calling the DB again, but it's not working as I expected, since while debugging i figured out the control moves to the next function which is the returnStuffToCaller
here's an idea of what's into my manipulateWithAsync function:
function manipulateWithAsync(rs) {
return rs.map( async function whoCares(singleRecord) {
let smthUseful = await getMoreData(singleRecord.someField);
singleRecord.enhancedField = smthUseful;
return singleRecord;
})
}
I get the point of this behaviour: the map function does work as expected and the promise chain doesn't give a duck about it since it's not working with the awaits.
Is there a way to allow my returnStuffToCaller function to wait till the async function did his job?
I also use bluebird and i tried to use coo-routine, so if you thing that it's a good solution I'll post my bluebird coo-routine failing code :)
Thanks!
The problem is in using async/await with Array.map
This answer should help: https://stackoverflow.com/a/40140562/5783272
rs.map iterator jumps to the next element without waiting in each separate iteration.
You need something like asyncMap
You can use - https://github.com/caolan/async
or either implement yourself
async function asyncMap(array, cb) {
for (let index = 0; index < array.length; index++) {
return await cb(array[index], index, array);
}
}
*cb function must be async one
Wrap your map with Promise.all return the Promise then await for the results wherever you call the manipulateWithAsync.
// MOCKS FOR DEMO
// Test data used as input for manipulateWithAsync
const testData = [
{ recordNumber: 1 },
{ recordNumber: 2 },
{ recordNumber: 3 }
];
// Mock function which returns Promises which resolve after random delay ranging from 1 - 3 seconds
const getMoreData = () =>
new Promise(resolve => {
const calledAt = Date.now();
setTimeout(() => {
resolve({
usefulData: `Promise called at ${calledAt}`
});
}, Math.floor(Math.random() * 3000) + 1000);
});
// SOLUTION / ANSWER
const manipulateWithAsync = async rs =>
Promise.all(
rs.map(async singleRecord => {
const smthUseful = await getMoreData(singleRecord.someField);
// Instead of manipulating original data,
// which might cause some unwanted side effects going forward,
// instead return new objects
return { ...singleRecord, enhancedField: smthUseful };
})
);
await manipulateWithAsync(testData);

How to create a loop of promises

so i have a promise that collects data from a server but only collects 50 responses at a time. i have 250 responses to collect.
i could just concate promises together like below
new Promise((resolve, reject) => {
resolve(getResults.get())
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
})
.then((results) => {
totalResults.concat(results)
return getResults.get()
}).then((results) => {
totalResults.concat(results)
return getResults.get()
})
In this instance i only need 250 results so this seems a managable solution but is there a way of concating promises in a loop. so i run a loop 5 times and each time run the next promise.
Sorry i am new to promises and if this were callbacks this is what i would do.
If you want to loop and serialise the promises, not executing any other get calls once one fails, then try this loop:
async function getAllResults() { // returns a promise for 250 results
let totalResults = [];
try {
for (let i = 0; i < 5; i++) {
totalResults.push(...await getResults.get());
}
} catch(e) {};
return totalResults;
}
This uses the EcmaScript2017 async and await syntax. When not available, chain the promises with then:
function getAllResults() {
let totalResults = [];
let prom = Promise.resolve([]);
for (let i = 0; i < 5; i++) {
prom = prom.then(results => {
totalResults = totalResults.concat(results);
return getResults.get();
});
}
return prom.then(results => totalResults.concat(results));
}
Note that you should avoid the promise construction anti-pattern. It is not necessary to use new Promise here.
Also consider adding a .catch() call on the promise returned by the above function, to deal with error conditions.
Finally, be aware that concat does not modify the array you call it on. It returns the concatenated array, so you need to assign that return value. In your code you don't assign the return value, so the call has no effect.
See also JavaScript ES6 promise for loop.
Probably you just need Promise.all method.
For every request you should create a promise and put it in an array, then you wrap everything in all method and you're done.
Example (assuming that getResults.get returns a promise):
let promiseChain = [];
for(let i = 0; i <5; i++){
promiseChain.push(getResults.get());
}
Promise.all(promiseChain)
.then(callback)
You can read more about this method here:
Promise.all at MDN
EDIT
You can access data returned by the promises this way:
function callback(data){
doSomething(data[0]) //data from the first promise in the chain
...
doEventuallySomethingElse(data[4]) //data from the last promise
}

Categories