How can I wait for multiple Promise.all() - javascript

I'm working with some old code and I'm trying to return results for two arrays of promises.
So basically body contains first and second which are arrays of ids. I know that the below code works for a small array of of 1 to 5 ids, but if I were to do a few hundred, would the resolve fire before I get the hundred of results from both promise arrays?
const doSomething = (body) => {
return new Promise((resolve, reject) => {
const promisesOne = body.first.map((id) =>
doSomethingOne(id)
);
const promisesTwo = body.second.map((id) =>
doSomethingTwo(id)
);
let response = {
first: {}
second: {}
headers: 'mock headers'
};
Promise.all(promisesOne).then((results) => {
response.first = results;
});
Promise.all(promisesTwo).then((results) => {
response.second = results;
});
resolve(response);
});
};
Also I won't be able to refactor this to async/await as this codebase does not use it.

would the resolve fire before I get the ... results from both promise arrays?
Yes, absolutely because you aren't waiting for them to resolve.
Simply wrap the two Promise.all() promises in another Promise.all() to wait for everything then create the final object you want.
const doSomething = (body) => {
return Promise.all([
Promise.all(body.first.map(doSomethingOne)),
Promise.all(body.second.map(doSomethingTwo)),
]).then(([first, second]) => ({
first,
second,
headers: "mock headers",
}));
};
You could make this generic by creating a map of body property names to their corresponding mapping function. For example
const mappers = {
first: doSomethingOne,
second: doSomethingTwo,
};
const doSomething = (body) => {
return Promise.all(
Object.entries(mappers).map(([key, mapper]) =>
Promise.all(body[key].map(mapper)).then((data) => ({ [key]: data }))
)
)
.then((results) => Object.assign({ headers: "mock headers" }, ...results));
};

Your code will not work at the moment - the resolve(response) will run before either of the Promise.alls are done.
First, refactor your .first and .second (etc) properties so that they're arrays, rather than unique string properties. Then, it'll be easy to .map them with two Promise.alls - one to wait for an individual array to fulfill, and an outer one to wait for all arrays to fulfill.
The same thing should be done for the doSomethingOne etc functions - to make the code easy to work with, use an array of functions instead of having many standalone identifiers.
Also, there's no need for the explicit Promise construction antipattern.
const doSomethings = [
doSomethingOne, // define these functions here
doSomethingTwo,
];
const doSomething = (body) => Promise.all(
body.arrayOfArraysOfIds.map(
(arr, i) => Promise.all(
arr.map(id => doSomethings[i](id))
)
)
)
.then((results) => ({
headers: 'mock headers',
results,
});

Related

Can I use this implementation of Promise.allSettled before ES2020?

I am working with a broken codebase so I am trying to touch as little as possible. In particular, right now it's using TypeScript and ES6, and I need to launch an array of promises and wait for them all to finish before I move on with the code execution, regardless of if they resolve or reject. So this is the usecase for Promise.allSettled, which is only available in ES2020.
I tried the following implementation:
const myPromiseAllSettled = (promises) => new Promise((resolve, reject) => {
const results = []
const settle = (result) => {
results.push(result)
if (results.length === promises.length) {
(results.every(value => value) ? resolve : reject)(promises)
}
}
promises.forEach(promise => {
promise
.then(() => settle(true))
.catch(() => settle(false))
})
})
but I have only seen my own code when it comes to promises, so I would like to get some feedback on my implementation. Does it do what other developers would expect it to do? Especially when it comes to the arguments passed to resolve/reject; right now I only pass the array of promises and expect the developer to run a .then(promises => promises.forEach(...)) if they are interested in following up on the individual promises.
I also don't know ideally how I would handle the types with TypeScript here, since I am not so experienced with TypeScript as well. (Writing 'any' everywhere doesn't seem cool to me.)
it should look more like this:
const myPromiseAllSettled = (promises) => {
const fulfilled = value => ({ status: "fulfilled", value });
const rejected = reason => ({ status: "rejected", reason });
return Promise.all([...promises].map(p => Promise.resolve(p).then(fulfilled, rejected)));
}
[...promises] to handle cases where promises is iterable but not an array.
Promise.resolve(p) because the passed value may be not a Promise (or thenable).
If you simply want to be notified about the success, you can do something simpler:
const success = await Promise.all(promises).then(() => true, () => false);
Edit: Didn't like the way handled promises may be an iterable but no array. Adding a version that Array#maps over iterables:
function* map(iterable, callback) {
for (const value of iterable) {
yield callback(value);
}
}
const myPromiseAllSettled = (promises) => {
const fulfilled = value => ({ status: "fulfilled", value });
const rejected = reason => ({ status: "rejected", reason });
return Promise.all(
map(
promises,
p => Promise.resolve(p).then(fulfilled, rejected)
)
);
}

wait for Cypress each() function to finish

I am calling a function populateArray which picks elements on the page using children() function. I want to store the attribute values into an using each() function.
Here is what I am doing
static populateArray(){
let arr = []
cy.xpath(NODE_PREVIEW_PANEL).children(NODE_TYPE)
.each((element, index, list) => arr.push(cy.wrap(element).invoke('attr', 'data-testid')))
}
The problem is when I call this function by assigning it to a variable
actualArray = ArticlePage.populateArray()
its not waiting for underlying each() function to complete fully. It just pick partial values and proceeds. I want actualArray to have values only after populateArray is fully resolved.
How about using Cypress.Promise? From the docs
Cypress is promise aware so if you return a promise from inside of commands like .then(), Cypress will not continue until those promises resolve
const populateArray = () => {
return new Cypress.Promise((resolve, reject) => {
let arr = []
cy.xpath('//ul')
.children('li')
.each(element => arr.push(element.attr('data-testid'))) // NOTE different push
.then(() => {
return resolve(arr)
})
})
}
Call with await (test must be async),
it('gets the array', async () => {
const actualArray = await ArticlePage.populateArray();
expect(actualArray).to.deep.equal(['1', '2', '3']); // whatever is in 'data-testid'
})
Since .each() can be chained - you can chain a .then and just type the code you need to execute after the .each() process is done:
static populateArray(){
let arr = []
cy.xpath(NODE_PREVIEW_PANEL).children(NODE_TYPE)
.each((element, index, list) => arr.push(arr.push(element.attr('data-testid')))
.then(list => {
cy.wrap(arr).as('actualArray')
})
}
And in test code
populateArray()
cy.get('#actualArray')
.then(actualArray => {
//code that needs the array data
})
Another answer using custom commands
Cypress.Commands.add("populateArray", (parentSelector, childSelector, arrayAllias) => {
let arr = []
cy.xpath(parentSelector).children(childSelector)
.each((element, index, list) => arr.push(element.attr('data-testid')))
.then(()=>{
cy.wrap(arr).as(arrayAllias)
})
})
And in test code
cy.populateArray(NODE_PREVIEW_PANEL, NODE_TYPE, 'actualArray')
cy.get('#actualArray')
.then(actualArray => {
//code that needs the array data
})

Array.filter() with async arrow function

I am trying to filter my array using Array.filter() function, however I came across this issue.
I need to call some other function inside the filter function asynchronously. However the array is not changing its value based on the conditions that I define in the function.
const filterWithEmail = data.filter(async (doc) =>
{
const findUser = await UserService.findUser(doc.id).catch(err => {});
if (findUser)
{
const { email } = findUser;
return regexFilter ? regexFilter.test(email.normalize("NFKC")) : false;
}
});
This code doesn't affect the data array at all for some reason.
Any suggestions what I'm doing wrong?
Thank you in advance.
filter expects the return value of the callback to be a boolean but async functions always return a promise.
You don't know if you want to return true or false in time to tell filter which it is.
What you possibly want to do is:
map the data from data to { keep: true, original_data: data } (using an async callback)
Pass the resulting array of promises to Promise.all
await the return value of Promise.all
filter that array with: .filter(data => data.keep)
Get the original objects back with .map(data => data.original_data)
Something along these lines (untested):
const filterWithEmail = (
await Promise.all(
data.map(async (data) => {
const findUser = await UserService.findUser(doc.id).catch((err) => {});
let keep = false;
if (findUser && regexFilter)
keep = regexFilter.test(email.normalize("NFKC"));
return { data, keep };
})
)
)
.filter((data) => data.keep)
.map((data) => data.data);

Using Async Funtions in a For loop

I have an array where I need to call an API endpoint for each index. Once that is resolved I need to append it in that element. I want to return the updated array once this gets completed for each index of the array.
I tried using async-await in this way
// Let input be [{'x': 1, 'y': 2}, {'x': 11, 'y': 22}, ...]
async function hello(input) {
await input.forEach(element => {
fetch(url, options)
.then((res) => {
element['z'] = res
})
})
return input
}
I need to use this function to update my state
hello(data)
.then((res: any) => {
this.setState((prevState) => ({
...prevState,
inputData: res,
}))
})
The issue is that I need one more forced render for key 'z' to show.
How to resolve this?
I don't have much experience using async await so I am not sure if I am using it correctly.
The correct way is to use Promise.all and return the promise to be used by the caller function since you want the entire updated input value to be set in state.
In your case forEach doesn't return a promise so await on it is useless.
Also if you use await within the forEach function, you need to be able provide away to let the hello function's .then method call when all promises have resolved. Promise.all does that for you
function hello(input) {
const promises = [];
input.forEach(element => {
promises.push(
fetch(url, options)
.then(res => res.json()
.then((result) => {
// return the updated object
return {...element, z: result};
})
)
});
return Promise.all(promises);
}
...
hello(data)
.then((res: any) => {
this.setState((prevState) => ({
...prevState,
inputData: res,
}))
})
P.S. Note that the response from fetch also will need to be called with res.json()
async/await won't work in loops which uses callback(forEach, map, etc...)
You can achieve your result using for..of loop.
Try this and let me know if it works.
function getResult() {
return new Promise((resolve) => {
fetch(url, options)
.then((res) => {
return resolve(res);
})
})
}
async function hello(input) {
for (let element of input) {
let res = await getResult(element);
element['z'] = res;
}
}

Equivalent of BlueBird Promise.props for ES6 Promises?

I would like to wait for a map of word to Promise to finish. BlueBird has Promise.props which accomplishes this, but is there a clean way to do this in regular javascript? I think I can make a new object which houses both the word and the Promise, get an array of Promises of those objects, and then call Promise.all and put them in the map, but it seems like overkill.
An implementation of Bluebird.props that works on plain objects:
/**
* This function maps `{a: somePromise}` to a promise that
* resolves with `{a: resolvedValue}`.
* #param {object} obj
* #returns {Promise<object>}
*/
function makePromiseFromObject(obj) {
const keys = Object.keys(obj);
const values = Object.values(obj);
return Promise.all(values)
.then(resolved => {
const res = {};
for (let i = 0; i < keys.length; i += 1) {
res[keys[i]] = resolved[i];
}
return res;
})
}
If you are dealing with a Map with values that are promises (or a mix of promises and non-promises) - and you want the final resolved value to be a Map with all values resolved
const mapPromise = map =>
Promise.all(Array.from(map.entries()).map(([key, value]) => Promise.resolve(value).then(value => ({key, value}))))
.then(results => {
const ret = new Map();
results.forEach(({key, value}) => ret.set(key, value));
return ret;
});
Although, I bet someone has a slicker way to do this, some of the new ES2015+ stuff is still new to me :p
The venerable async.js library has a promisified counterpart: async-q
The promisified async-q library supports all the functions in the async library. Specifically async.parallel(). At first glance async.parallel() looks just like Promise.all() in accepting an array of functions (note one difference, an array of functions, not promises) and run them in parallel. What makes async.parallel special is that it also accepts an object:
const asyncq = require('async-q');
async function foo () {
const results = await asyncq.parallel({
something: asyncFunction,
somethingElse: () => anotherAsyncFunction('some argument')
});
console.log(results.something);
console.log(results.somethingElse);
}
Alternative implementation combining ES6+ Object.entries() and Object.fromEntries():
async function pprops(input) {
return Object.fromEntries(
await Promise.all(
Object.entries(input)
.map(
([k, p])=>p.then(v=>[k, v])
)
)
);
};
I have two different implementations using ES6 async functions:
async function PromiseAllProps(object) {
const values = await Promise.all(Object.values(object));
Object.keys(object).forEach((key, i) => object[key] = values[i]);
return object;
}
One line shorter, but less optimized:
async function PromiseAllProps(object) {
const values = await Promise.all(Object.values(object));
return Object.fromEntries(Object.keys(object).map((prop, i) => ([prop, values[i]])));
}
Example
const info = await PromiseAllProps({
name: fetch('/name'),
phone: fetch('/phone'),
text: fetch('/foo'),
});
console.log(info);
{
name: "John Appleseed",
phone: "5551234",
text: "Hello World"
}
It would be advisable to use a library like bluebird for this. If you really want to do this yourself, the main idea is to:
Resolve each of the map values and connect the promised value back with the corresponding key
Pass those promises to Promise.all
Convert the final promised array back to a Map
I would make use of the second argument of Array.from, and the fact that an array of key/value pairs can be passed to the Map constructor:
Promise.allMap = function(map) {
return Promise.all( Array.from(map,
([key, promise]) => Promise.resolve(promise).then(value => [key, value])
) ).then( results => new Map(results));
}
// Example data
const map = new Map([
["Planet", Promise.resolve("Earth")],
["Star", Promise.resolve("Sun")],
["Galaxy", Promise.resolve("Milky Way")],
["Galaxy Group", Promise.resolve("Local Group")]
]);
// Resolve map values
Promise.allMap(map).then( result => console.log([...result]) );
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can simply write it using Promise.all + reduce
const promiseProps = (props) => Promise.all(Object.values(props)).then(
(values) => Object.keys(props).reduce((acc, prop, index) => {
acc[prop] = values[index];
return acc;
}, {})
);
And a nice lodash variant for sake of completeness for plain js objects
async function makePromiseFromObject(obj: {[key: string]: Promise<any>}) {
return _.zipObject(Object.keys(obj), await Promise.all(Object.values(obj)))
}

Categories