// obviously an age can be calculated synchronously, this is just an example
// of a function that might be asynchronous.
let getAge = (user) => {
return new Promise((resolve, reject) => {
setTimeout(function () {
resolve((new Date()).getFullYear() - user.birthYear);
}, 100);
});
};
function getUserFields(user, fieldArray) {
if (!fieldArray) {
// no field filtering/generating, just return the object as
// a resolved promise.
return Promise.resolve(user);
// Note: If you want to make sure this is copy of the data,
// not just a reference to the original object you could instead
// use Object.assign to return it:
//
// return Promise.resolve(Object.assign({}, user));
} else {
// Each time we grab a field, we are either store it in the array right away
// if it is synchronous, if it is asyncronous, create a thenable representing
// its eventual value and store that.
// This array will hold them so we can later pass them into Promise.all
let fieldData = [];
// loop over all the fields we want to collect
fieldArray.forEach(fieldName => {
// our special 'age' field doesn't exist on the object but can
// be generated using the .birthYear
if (fieldName === 'age') {
// getAge returns a promise, we attach a then to it and store
// that then in fieldData array
let ageThenable = getAge(user).then((result) => {
// returning a value inside of then will resolve this
// then to that value
return [fieldName, result];
});
// add our thenable to the promise array
fieldData.push(ageThenable);
} else {
// if we don't need to do anything asyncronous, just add the field info
// to the array
fieldData.push([fieldName, user[fieldName]]);;
}
});
// Promise.all will wait until all of the thenables to be resolved before
// firing it's then. This means if none of the fields we were looking for
// is asyncronous, it will fire right away. If some of them were
// asyncronous, it will wait until they all return a value before firing.
// We are returning the then, which will transform the collected data into
// an object.
return Promise.all(fieldData).then(fields => {
// fields is an array of 2-element arrays containing the field name
// and field value for each field we are collecting. We will loop over
// this array with reduce and transform them into an object containing
// all of the properties we collected.
let newUserObj = fields.reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
// Above I am using destructuring to get the key/value if the browsers
// you are targeting don't support destructuring, you can instead just
// give it a name as an array and then get the key/value from that array:
//
// let newUserObj = fields.reduce((acc, fieldData) => {
// let key = fieldData[0],
// value = fieldData[1];
// acc[key] = value;
// return acc;
// }, {});
// return new object we created to resolve the then we returned
return newUserObj;
});
}
}
let users = [
{
name: 'Tom',
birthYear: 1986
},
{
name: 'Dick',
birthYear: 1976
},
{
name: 'Harry',
birthYear: 1997
}
];
// generate a new user object with an age field
getUserFields(users[0], ['name', 'birthYear', 'age']).then(user => {
console.dir(user);
});
// generate a new user object with just the name
getUserFields(users[1], ['name']).then(user => {
console.dir(user);
});
// just get the user info with no filtering or generated properties
getUserFields(users[2]).then(user => {
console.dir(user);
});