Reputation: 89
I have an object that has properties that are generated via async functions. I feel like it is calling one, then calling the next, etc. I don't need it to do that but I do need it all be complete before moving on. Below is how I currently do it but I am looking for a more efficient method. I have thought about calling all the functions using Promise.All() in variables. Then creating the object and setting the properties to the variables but I have a feeling someone has a more elegant solution somewhere.
let obj = {
prop1 : await functionName(something),
prop2 : await anotherFunction(somethingElse)}
Upvotes: 1
Views: 100
Reputation: 590
This function match the behavior and signature of Object.assign()
but handles values that are Promises
.
/**
* @param {{[key: PropertyKey]: unknown}} target
* @param {...{[key: PropertyKey]: unknown | Promise<unknown>}} asyncSources
*/
async function assignAsync(target, ...asyncSources) {
/**
* @param {{[key: PropertyKey]: unknown}} target
* @param {PropertyKey} property
* @returns {boolean}
*/
const propertyIsEnumerable = (target, property) => {
return Object.prototype.propertyIsEnumerable.call(target, property)
}
const sources = await Promise.all(
asyncSources
.filter(asyncSource => asyncSource != null)
.map(async asyncSource => {
const asyncSourceOwnEnumerableProperties = (
Reflect.ownKeys(asyncSource)
.filter(property => propertyIsEnumerable(asyncSource, property))
)
/**
* @type {{[key: PropertyKey]: unknown}}
*/
const source = {}
await Promise.all(
asyncSourceOwnEnumerableProperties.map(property => {
const valueOrPromise = asyncSource[property]
if (valueOrPromise instanceof Promise) {
return valueOrPromise.then(value => {
source[property] = value
})
}
source[property] = valueOrPromise
})
)
return source
})
)
Object.assign(target, ...sources)
return target
}
Usage
const data = await assignAsync({}, {
apiValue_1: fetchAPI_1(),
apiValue_2: fetchAPI_2(),
})
console.log(data)
Interesting question. I was thinking and come up with 2 solutions.
The 1st one is by using Promise.all
as you mentioned:
function delay(ms = 0) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function functionName() {
await delay(1000)
return 'value1'
}
async function anotherFunction() {
await delay(500)
return 'value2'
}
async function createAsyncObject() {
const obj = {}
// Set properties
await Promise.all([
functionName().then(v => obj.prop1 = v),
anotherFunction().then(v => obj.prop2 = v)
])
return obj
}
const obj = await createAsyncObject()
The 2nd one I make use of Proxy
object to make it handle the Promises
to set the object's properties:
function delay(ms = 0) {
return new Promise(resolve => setTimeout(resolve, ms))
}
async function functionName() {
await delay(1000)
return 'value1'
}
async function anotherFunction() {
await delay(500)
return 'value2'
}
function asyncObjectWrapper(obj) {
const promises = []
const then = function then(callback) {
return Promise.all(promises).then(() => callback())
}
return new Proxy(obj, {
get: (target, property, receiver) => {
if (property !== 'then') return
return then
},
set: (target, property, value) => {
if (!(value instanceof Promise)) return true
promises.push(value)
value.then(v => target[property] = v)
return true
}
})
}
async function createAsyncObject() {
const obj = {}
const wrapper = asyncObjectWrapper(obj)
// Set properties
await Object.assign(wrapper, {
prop1: functionName(),
prop2: anotherFunction()
})
return obj
}
const obj = await createAsyncObject()
Upvotes: 2
Reputation: 30705
You could create a list of name, value pairs by using Promise.all()
and your property retrieval functions.
Once you have this list of entries you can use Object.fromEntries()
to create your new object.
In this example, the property retrieval functions are getA()
, getB()
and getC()
, they could, of course be called anything.
function getA() {
return new Promise(resolve => setTimeout(resolve, 500, 'value a'))
}
function getB() {
return new Promise(resolve => setTimeout(resolve, 500, 'value b'))
}
function getC() {
return new Promise(resolve => setTimeout(resolve, 500, 'value c'))
}
async function getProperty(name, fn) {
let value = await fn();
return [name, value];
}
async function getObj() {
console.log('Getting properties...');
const entries = await Promise.all([ getProperty('a', getA), getProperty('b', getB), getProperty('c', getC) ]);
console.log('Properties:', entries);
const newObj = Object.fromEntries(entries);
console.log('New obj:', newObj)
}
getObj()
.as-console-wrapper { max-height: 100% !important; }
Upvotes: 1