I would like to return a list of promises with Promise.all, but the result is always empty.
Here is my theoric code:
function myFunction() {
const pSqlLines = sql.openDatabase().then(mdb => {
const query = `SELECT data FROM database`
return mdb.prepare(query).all()
})
const globalPromise = new Promise( resolve => {
const promises = []
pSqlLines.then( sqlLines => {
sqlLines.forEach(line => {
promiseA()
.then(res1 => { if (res1 == 1) return promiseB() })
.then(res2 => { if (res2 == 1) return promiseC() })
.then(res3 => { if (res3 == 1) promises.push( new Promise(resolve => resolve(line) ) ) })
}) // The promises chaining is correct as the 'promises' array is correctly fulfilled, but later
resolve( Promise.all(promises) ); // It is always called before the foreach loop. Why?
})
})
return globalPromise.then( result => console.log(result) )
}
Someone can help me please?
Thanks a lot.
You should not need to create a new promise with new Promise when you already have a promise to work with -- in your case: pSqlLines. Wrapping pSqlLines inside a new Promise wrapper is an example of the promise constructor antipattern.
Secondly, this code:
new Promise(resolve => resolve(line) )
...can be replaced with:
Promise.resolve(line)
...and since it is to serve as the return value for a then callback, it can be just:
line
As to your question: you are indeed calling Promise.all(promises) at a moment that the promises array is still empty, as the forEach loop has only executed Promise() and its then chain, but none of the asynchronous callbacks that are passed to those then calls have executed yet. You didn't await the resolution of these promises.
I get from your code that you want to exclude some line values depending on conditions. In that case the promise will resolve to undefined. I suppose you would want to exclude those undefined values, and so maybe a filter(Boolean) is appropriate.
Here is a theoretical solution for your theoretical code:
function myFunction() {
const pSqlLines = sql.openDatabase().then(mdb => {
const query = `SELECT data FROM database`;
return mdb.prepare(query).all();
});
return pSqlLines.then( sqlLines => {
const promises = sqlLines.map(line => {
return promiseA()
.then(res1 => { if (res1 == 1) return promiseB(); })
.then(res2 => { if (res2 == 1) return promiseC(); })
.then(res3 => { if (res3 == 1) return line; });
}));
return Promise.all(promises);
}).then(result => result.filter(Boolean));
}
Related
I have an array of promises that looks like this:
const content = await getData() //gets data from API
const drinkURL = content.drinks.map( item => "lookup.php?i=" + item.idDrink) // gets element in the API end-point
const getInfo = async() => {
let allPromises = Promise.all(drinkURL.map(item => getData(item)))
allPromises.catch(e => console.log(e)) // Trying to catch any errors here, but can't.
return allPromises
}
I want to add a catch statement in case any of the promises can not resolve.
I have tried writing it inside the getInfo function, and after the .then() inside the next snippet.
const view = `
<div class="Drink-card">
Home
${await getInfo()
.then(ele => ele.map(item => item.drinks.map( drink => `
<article class="Drink-main">
<img src="${drink.strDrinkThumb}" alt="${drink.strDrink}">
<h2>${drink.strDrink}</h2>
</article>
`)).join("")).catch(e => console.log(e))//Does not work in here either}
I have tried a bunch of ways but can't come up with a working result.
Thanks!
You should use await keyword (1) combines with catch inside of each promise (2) as the following simple sample :
Full code sample below:
function getData(object){
return new Promise(resolve => {
if(object.value === "3")
throw new Error("Whoops!")
else
resolve(object.value);
});
}
var drinkURLs = [{ value: "1" }, { value: "2" }, { value: "3" }];
var getInfo = async() => {
var promises = drinkURLs.map(item => getData(item));
var results = await Promise.all(promises.map(p => p.catch(e => e)));
var inValidResults = results.filter(result => (result instanceof Error));
var validResults = results.filter(result => !(result instanceof Error));
console.log({inValidResults, validResults});
return validResults;
}
await getInfo();
You can use Promise.allSettled() function.
The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise while Promise.all rejects immediately upon any of the input promises rejecting or non-promises throwing an error, and will reject with this first rejection message / error.
I am trying to understand Promise.all here.
What I did here was to covert below code using Promise.all to achieve the same result.
I understand that Promise all combine the data1, data2.
My question here is that how does Promise.All work without resolve method?
Does Promise resolve those data within the method itself?
Please advise.
const readAllUsersChaining = () => {
return new Promise((resolve, reject) => {
let result = [];
getDataFromFilePromise(user1Path)
.then((data) => {
result.push(JSON.parse(data)); // what are you doing? he's gone mad...
return getDataFromFilePromise(user2Path);
})
.then((data) => {
result.push(JSON.parse(data));
result ? resolve(result) : reject(result);
});
});
};
const readAllUsers = () => {
const data1 = getDataFromFilePromise(user1Path);
const data2 = getDataFromFilePromise(user2Path);
console.log(data1, data2);
return Promise.all([data1, data2]).then((data) => {
return data.map((el) => JSON.parse(el));
});
};
My question here is that how does Promise.All work without resolve method?
Not quite sure what you mean. Promise.all simply creates a new promise internally that is resolved when all other promises are resolved.
Here is a simple implementation of Promise.all for the case that arguments are always promises:
function all(promises) {
if (promises.length === 0) {
return Promise.resolve([]);
}
return new Promise((resolve, reject) => {
const results = [];
let resolved = 0;
promises.forEach((promise, i) => {
promise.then(
result => {
results[i] = result;
resolved++;
if (resolved === promised.length) {
resolve(results);
}
},
error => reject(error)
);
});
}
Promise.all allows to wait until all promise passed as arguments to be fullfilled before the then method attach to be execute.
The real use case would be when you have to perform five call to an API, an you want to perform some treatement only when you have get the data from all the API call. you can rely on the Promise.all function which will wait all passed promised to be fullfilled for it to be fullfiled on it turn.
Bellow I provide an example of a Promise.all call. which has two Promises passed as argument. the first one has a timer which fullfilled it after 5 second and the second if fullfiled immediately but. the Promise.all will be fullfilled only when both of the promise passed as argument ar fullfilled
const firstPromise = new Promise((resolve, reject) => {
setTimeout(()=> {
return resolve({
name: "First Promise"
});
}, 5000);
});
const secondPromise = new Promise((resolve, reject) => {
return resolve({
name: "Second Promise"
});
})
const all = Promise.all([firstPromise, secondPromise]).then((response) => {
console.log(response);
});
I'm trying to generate an array of Promises to run sequentially. I've seen lots of tips on this but can't get it to run in my use case.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget')
const promises = containers.map(child => processIcon(child))
promises.reduce((p, fn) => p.then(fn), Promise.resolve())
resolve()
})
}
function processIcon(child){
return new Promise((resolve, reject) => html2canvas(child).
then(canvas => uploadFromCanvas(canvas,
child.childNodes[0].className.split(' ')[1] + '.png'))
.then(resolve).catch(reject))
}
Any tips? This just rejects and I can't see why
Looks like you want to resolve the main promise when the canvases are available for all the child elements. You can use Promise.All() for this.
It should also be noted that the querySelectorAll doesn't return anything you can call the .map on. You will have to create an array from what the querySelectorAll returns - which is a NodeList.
export default function generateIcons(){
return new Promise((resolve, reject) => {
const containers = document.querySelectorAll('.html2CanvasTarget');
const promises = Array.from(containers).map(child => processIcon(child))
Promises.All(promises).then(() => resolve());
})
}
containers is a NodeList, and NodeLists don't have a .map method, which is why your code is throwing an error.
Because processIcon already returns a Promise, there's no need to use the Promise constructor again. html2canvas already returns a Promise too, so there's no need for any Promise constructor anywhere (see What is the explicit promise construction antipattern and how do I avoid it?)
If possible, just await each call of it in a for loop. Because uploadFromCanvas returns a Promise too, and you want to wait for it, return it (or await it) as well:
export default async function generateIcons() {
const containers = document.querySelectorAll('.html2CanvasTarget');
for (const container of containers) {
await processIcon(container);
}
}
function processIcon(child) {
return html2canvas(child, {backgroundColor:null})
.then(canvas => uploadFromCanvas(canvas, child.className.split(' ')[1] + '.png'))
.catch(console.log);
}
As per your question, you can use Q module module for that
You need to take an empty array and push promises into it, and just pass this array in Q method (Q.allSettled), Take a look with an example
const Q = require('q');
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Q.allSettled(promises)
.then((results) => {
results.forEach((result) => {
if (result.state === 'fulfilled') {
const value = result.value;
return value;
}
const reason = result.reason;
throw reason;
});
});
In Q.allSettled() The method you always get the result in .then(). There are 2 states. One for success and one for failure.
Success => state === 'fulfilled', value: 'Whatever your promise return'
Failure => state === 'rejected', reason: 'Whatever your promise thrown'
In this case, you have a number of successful and unsuccessful promises.
There is the second approach which is Promise.all() do the same but the issue is whenever any of promise rejected further promise never called.
const promiseHolder = [];
for (let i = 0; i < 10; i += 1) {
promiseHolder.push('Your Promises');
}
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
throw err;
});
In the second approach ( Promise.all()), It consists of all your promises pushed from for loop. If any of promise rejected no more promise called and suddenly you got the state of promise rejection in Promise.all().
Promise.all(promiseHolder)
.then((results) => {
return results;
})
.catch((err) => {
console.log('Promise will reject here', err);
throw err;
});
I hope it helps, Happy Coding :)
This code works as expected:
services.map(async svc => {
promises.push(new Promise(async (resolve) => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
resolve();
}));
});
await Promise.all(promises).then(() => {
return res.send(html);
})
Why does the code below not work? In my eyes it's the same, but the execution order is now incorrect.
Promise.all([
services.map(async svc => {
new Promise(async (resolve) => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
resolve();
})
})
]).then(() => {
// done - called before finished in this version
}).catch(() => {
// err
});
I believe the primary reason that your code doesn't work is that you are passing an array of arrays ([services.map(...)]) to Promise.all, not an array of promises.
However, the code is unnecessarily complex. There is no need to create a promise inside an async function, async functions always return a promise. It should just be:
Promise.all( // <- note the removed [...]
services.map(async svc => {
html += `<h2>${svc}</h2>`;
let journeyDetails = await admin.database().ref(`data`).once('value');
// more code here
})
)
Is it possible for Promise.all to return the last value of the chain without a wrapper promise?
Without using await, it doesn't work in my context
Without wrapper example :
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),3000)
})
}
const p1 = sum1(1);
p1
.then(sum1)
.then(sum1)
Promise.all([p1])
.then(v => console.log(v[0]));
It logs 2 instead of the expected 4.
But if I use a wrapper it works :
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),3000)
})
}
function sum3(x){
return sum1(x)
.then(sum1)
.then(sum1)
}
const p2 = sum3(1);
Promise.all([p2])
.then(v => console.log(v[0]));
But in my context it gets complicated if I need to create and name a wrapper function for every chain of promises...
Is this possible?
You can store the value returned by p1.then(sum1).then(sum1) and call Promise.all on this value. It waits for the resolution of the promise chain not only the first one. Here is an example:
function sum1(x) {
return new Promise(resolve => {
setTimeout(t => resolve(x + 1), 10);
});
}
const p1 = sum1(1);
const p2 = p1.then(sum1).then(sum1);
Promise.all([p1]).then(v => console.log('P1', v[0]));
Promise.all([p2]).then(v => console.log('P2', v[0]));
Actually all I had to do was call the chain in the variable declaration, so it references the last called promise
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),3000)
})
}
//The change is here
const p1 = sum1(1)
.then(sum1)
.then(sum1)
Promise.all([p1])
.then(v => console.log(v[0]));
Explanation: the problem with your code was that you store in const p1 = sum1(1); only first part of chain, and in Promise.all([p1]) you get result only from this first part (one solution is just to store all chain in p1 like this: p1=sum1(1).then(sum1).then(sum1). However in your case, you don't need to use Promie.all at all (because in your example there is only one promise p1/2 ):
function sum1(x){
return new Promise(resolve => {
setTimeout(t => resolve(x+1),300)
})
}
// Promise.all([sum1(1).then(sum1).then(sum1)]).then(r => console.log(r)); // this works too
sum1(1).then(sum1).then(sum1).then(r => console.log(r));
How about create a new function to do your task, it seems like promise.all does not fit to your case
const runInWaterfall = (promises) => new Promise((resolve, reject) => {
const result = promises.reduce((acc, curr, index) => {
if(!acc) {
return curr();
}
return acc.then(curr);
}, null);
result.then(resolve).catch(reject);
})
and your task can be rewritten as following
runInWaterfall([() => Promise.resolve(1), sum1, sum1, sum1]).then(result => console.log(result))