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)))
}
Related
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,
});
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);
I have the following statement:
for await (const blob of client.list()) {
console.log('\t', blob.name);
}
client.list() returns an async iterable iterator, and expects the use of for await...of to resolve the promises. I would like to incorporate the code into an existing rxjs pipe that instantiates the client.
I looked everywhere and I couldn't figure out how to do so without resolving the promise inside the pipe rather than converting into observables.
Any help would be appreciated!
I can't find an existing rxjs operator, but it doesn't seem too difficult to make your own. When integrating other APIs with observables you can interact with the API within the function passed to the observable constructor. This allows a lot of flexibility when triggering next/error/complete.
Edit - I've added a second option for doing this, using rxjs operators and avoiding explicitly calling next/error/complete.
const {
Observable,
operators,
from
} = rxjs;
const {take, takeWhile, expand, map, filter} = operators;
const asyncGen = async function*(x = -1) {
while(x++ < 5) {
yield x;
}
};
const fromAsyncIter = iterable => new Observable(subscriber => {
let unsubscribed = false;
const iterate = async () => {
try {
for await (let n of iterable) {
console.log('await', n);
subscriber.next(n);
if (unsubscribed) return;
}
subscriber.complete();
} catch (e) {
subscriber.error(e);
}
}
iterate();
return () => unsubscribed = true;
});
const fromAsyncIter2 = iterable =>
from(iterable.next()).pipe(
expand(() => iterable.next()),
takeWhile(x => !x.done),
map(x => x.value)
);
// const source = fromAsyncIter(asyncGen()).pipe(take(2));
const source = fromAsyncIter2(asyncGen()).pipe(take(2));
source.subscribe({
next: x => console.log('next', x),
error: e => console.error(e),
complete: () => console.log('complete')
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.4/rxjs.umd.js"></script>
Using Express, I am using .map to create an array of API calls, whose results I want to combine into one response object. Since every call uses different query parameters, I would like to use the query parameters as the response object's keys.
I create the GET requests using axios, which returns a promise, and then I use axios.all to wait for all of the promises to resolve.
The problem is that after the promises resolve, I no longer have access to the variables used to create them. How can I attach those variables to the promises for later reference?
Here is the API:
router.get('/api', (req, res) => {
const number = req.query.number;
res.json({ content: "Result for " + number });
});
Here is where I am trying to combine the results:
router.get('/array', async (req, res) => {
res.locals.payload = {};
const arr = [1, 2, 3, 4, 5];
const promises = arr.map(number => {
return axiosInstance.get('/api', { params: { number: number } })
});
const results = await axios.all(promises);
results.map(r => {
// number should match original, but I no longer have
// access to the original variable
number = 1;
res.locals.payload[number] = r.data;
});
res.json(res.locals.payload);
});
Result of GET on /array:
{
"1": {
"content": "Result for 5"
}
}
What can I do when creating the Promise objects to preserve the keys?
If the result were going to be an array with 0-based indexes instead of an object with properties "1", "2", etc., we could solve it with Promise.all (or axios.all if it offers the same guarantee Promise.all does that the array it gives you will be in the order of the promises you give it, regardless of the order in which they resolve). But I'm guessing your "number" is really a placeholder for something more interesting. :-)
You can do this by making use of the fact that a promise is a pipeline where the various handlers transform the contents along the way.
In this case, you can transform the result of the get call into an object with the number and result:
const promises = arr.map(number => {
return axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}));
});
Now, results in your all.then callback receives an array of objects with result and number properties. You could use destructuring parameters to receive them in your results.map callback if you like:
// vvvvvvvvvvvvvvvv---------------- destructuring parameters
results.map(({result, number}) => {
res.locals.payload[number] = result.data;
// ^^^^^^----^^^^^^-------- using them
});
Live Example:
// Fake axiosInstance
const axiosInstance = {
get(url, options) {
return new Promise(resolve => {
setTimeout(() => {
resolve({data: `Data for #${options.params.number}`});
}, 300 + Math.floor(Math.random() * 500));
});
}
};
// Fake axios.all:
const axios = {
all: Promise.all.bind(Promise)
};
// Fake router.get callback:
async function routerGet() {
const payload = {}; // stand-in for res.locals.payload
const arr = [1, 2, 3, 4, 5];
const promises = arr.map(
number => axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}))
);
const results = await axios.all(promises);
results.map(({result, number}) => {
/*res.locals.*/payload[number] = result.data;
});
console.log(/*res.locals.*/payload);
}
// Test it
routerGet().catch(e => { console.error(e); });
Note I've used ES2015+ property shorthand above, since you're using arrow functions and such. The object initializer { number } is exactly the same as { number: number }.
Side note 2: You can use a concise arrow function for the map callback if you like:
const promises = arr.map(
number => axiosInstance.get('/api', { params: { number } })
.then(result => ({result, number}))
);
While building custom endpoints I often need to resolve a complex object containing promises.
For illustration, take this example:
Given known user's id, employeeId and memberGroupsIds (an array):
var loginResponse = {
userprofile : getProfile(id)
companyInfo : {
company : getCompany(employeeId)
companyRelations : getPriviligedInfo(employeeId)
}
groups : getGroups(memberGroupsIds)
}
This logic works for synchronous functions that just return their values. But with functions that return promises I have to manually push all of them into an array to ensure they are resolved before using the final object.
I find the above code very easy to understand, and I'm looking for a signature that gives some of that, while still ensuring that the promises are resolved before sending a final object to the client.
The problem is not making it work, but making it beautiful and easy to read.
The best answer would ensure that the values are returned to the expected keys in the object and that all the promises are resolved in parallel, while maintaining a structure that is somewhat compatible with that of synchronous functions.
Or, if I'm missing the point and looking at this all wrong, how should I be looking at it?
You could use the helper function below. It takes an object and returns a promise that resolves when all nested promises have been resolved. The returned promise will provide as value the same object, which will have mutated with all its embedded promises replaced by their corresponding values.
function promiseRecursive(obj) {
const getPromises = obj =>
Object.keys(obj).reduce( (acc, key) =>
Object(obj[key]) !== obj[key]
? acc
: acc.concat(
typeof obj[key].then === "function"
? [[obj, key]]
: getPromises(obj[key])
)
, []);
const all = getPromises(obj);
return Promise.all(all.map(([obj, key]) => obj[key])).then( responses =>
(all.forEach( ([obj, key], i) => obj[key] = responses[i]), obj)
);
}
You would call it like this:
var loginResponsePromise = promiseRecursive({
userprofile : getProfile(10),
companyInfo : {
company : getCompany(101),
companyRelations : getPriviligedInfo(101)
},
groups : getGroups([5])
});
function promiseRecursive(obj) {
const getPromises = obj =>
Object.keys(obj).reduce( (acc, key) =>
Object(obj[key]) !== obj[key] ? acc
: acc.concat(typeof obj[key].then === "function" ? [[obj, key]]
: getPromises(obj[key]))
, []);
const all = getPromises(obj);
return Promise.all(all.map(([obj, key]) => obj[key])).then( responses =>
(all.forEach( ([obj, key], i) => obj[key] = responses[i]), obj)
);
}
// Example promise-returning functions
const wait = ms => new Promise( resolve => setTimeout(resolve, ms) ),
getProfile = id => wait(100).then(_ => ({userName: 'user' + id,id})),
getCompany = employeeId => wait(200).then(_ => ({employeeName: 'employee' + employeeId, employeeId})),
getPriviligedInfo = employeeId => wait(500).then(_ => ({privs: 'privInfo' + employeeId, employeeId})),
getGroups = memberGroupsIds => wait(400).then(_ => ({groups: ['group' + memberGroupsIds[0]],memberGroupsIds}));
// Sample input passed to `promiseRecursive` function
const loginResponsePromise = promiseRecursive({
userprofile : getProfile(10),
companyInfo : {
company : getCompany(101),
companyRelations : getPriviligedInfo(101)
},
groups : getGroups([5])
});
// Display the resolved object
loginResponsePromise.then( o => console.log(o) );
.as-console-wrapper { max-height: 100% !important; top: 0; }
I usually solve this kind of scenarios with Bluebird's join http://bluebirdjs.com/docs/api/promise.join.html :
const Promise = require('bluebird');
return Promise.join(
getProfile(id),
getCompany(employeeId),
getPrivilegedInfo(employeeId),
getGroups(memberGroupsIds),
(userProfile, company, companyRelations, groups) => {
return {
userProfile: userProfile,
companyInfo: {
company: company,
companyRelations: companyRelations
},
groups: groups
};
}
);
Using new ES6 features I would write something like this:
Promise.all([
getProfile(id),
getCompany(employeeId),
getPriviligedInfo(employeeId),
getGroups(memberGroupsIds)
])
.then(response => {
const [ userprofile, company, companyRelations, groups ] = response
const loginResponse = {
userprofile,
companyInfo : {
company,
companyRelations
},
groups
}
})
.catch(err => console.error(err))
Maybe the interesting part is that Promise.all() keep the input arguments order not depending on which resolves first. So in next step, using Destructuring Array assignment, the code looks like synchronous.