I'm looking for vocabulary or for a library that supports the following behaviour:
Imagine a Javascript object like the following one:
const foo = {
id: 1,
name: 'Some String value',
supplier: async () => {
return 'Some supplier name'
},
nested: async () => {
return [
{
id: 2,
name: async () => {
return 'this is a name'
}
}
]
}
}
It is composed by native types (numbers, strings...) and by functions.
I'd like this object being transformed to the following one:
const resolved = {
id: 1,
name: 'Some string value',
supplier: 'Some supplier name',
nested: [
{
id: 2,
name: 'this is a name'
}
]
}
As you see the transformed object does not have functions anymore but only native values.
If you are familiar with GraphQL resolvers, it might ring a bell to you.
I know I can write my own implementation of the behaviour but I'm sure this is something that already exists somewhere.
Do you have some keywords to share?
CodePudding user response:
I doubt there's a library that does exactly this, unless you're actually thinking of the graphql-js execute
method.
But it's easy enough to implement yourself:
async function initialised(value) {
if (typeof value == 'function') return initialised(await value());
if (typeof value != 'object' || !value) return value;
if (Array.isArray(value)) return Promise.all(value.map(initialised));
return Object.fromEntries(await Promise.all(Object.entries(value).map(([k, v]) =>
initialised(v).then(r => [k, r])
)));
}
async function initialised(value) {
if (typeof value == 'function') return initialised(await value());
if (typeof value != 'object' || !value) return value;
if (Array.isArray(value)) return Promise.all(value.map(initialised));
return Object.fromEntries(await Promise.all(Object.entries(value).map(([k, v]) =>
initialised(v).then(r => [k, r])
)));
}
const foo = {
id: 1,
name: 'Some String value',
supplier: async () => {
return 'Some supplier name'
},
nested: async () => {
return [
{
id: 2,
name: async () => {
return 'this is a name'
}
}
]
}
};
initialised(foo).then(resolved => {
console.log(resolved);
})
CodePudding user response:
Lazy initializable data-structures with deferred / asynchronous resolvable values like the one presented by the OP could be achieved by a two folded recursive approach where
one recursion is responsible for collecting all of an object's async entries/values
and the main recursion is responsible for creating and aggregating the resolved data-structure by awaiting all async values of the former collection and by calling itself again on each of the awaited results.
An implementation either could mutate the passed lazy initializable data-structure by resolving its deferred values, or it could create a deep but resolved copy of the former. The beneath provided code supports the resolved structured clone of the originally passed data.
function isAsynFunction(value) {
return (/^\[object\s AsyncFunction\]$/)
.test(Object.prototype.toString.call(value));
}
function isObject(value) {
return (value && ('object' === typeof value));
}
function collectAsyncEntriesRecursively(obj, resolved = {}) {
return Object
.entries(obj)
.reduce((result, [key, value]) => {
if (isAsynFunction(value)) {
result.push({ type: obj, key });
} else if (isObject(value)) {
result
.push(
...collectAsyncEntriesRecursively(value)
);
}
return result;
}, []);
}
async function resolveLazyInitializableObjectRecursively(obj) {
// - recursively aggregate (one layer after the other)
// a real but deferred resolved copy of the initially
// provided data-structure.
const deferred = Object.assign({}, obj);
// - delete the above assignement and replace every
// occurrence of `deferred` with `obj` in order to
// entirely mutate the initially provided data-structure.
const deferredEntries = collectAsyncEntriesRecursively(deferred);
if (deferredEntries.length >= 1) {
const results = await Promise
.all(
deferredEntries
.map(({ type, key }) => type[key]())
);
deferredEntries
.forEach(({ type, key }, idx) => type[key] = results[idx]);
// console.log({ results, deferred });
await resolveLazyInitializableObjectRecursively(results);
}
return deferred;
}
const foo = {
id: 1,
name: 'Some String value',
supplier: async () => {
return 'Some supplier name'
},
nested: async () => {
return [
{
id: 2,
name: async () => {
return 'this is a name'
}
}
]
}
};
const complexDeferred = {
id: 1,
name: 'Some String value',
supplier: async () => {
return 'Some supplier name'
},
nested: async () => {
return [{
id: 2,
name: async () => {
return 'this is a name'
}
}, {
id: 3,
name: async () => {
return 'this is another name'
}
}, {
id: 4,
nested: async () => {
return [{
id: 5,
name: async () => {
return 'this is yet another name'
}
}];
}
}];
}
};
(async () => {
const resolved = await resolveLazyInitializableObjectRecursively(foo);
console.log({ foo, resolved });
const complexResolved =
await resolveLazyInitializableObjectRecursively(complexDeferred);
console.log({ complexDeferred, complexResolved });
})();
.as-console-wrapper { min-height: 100%!important; top: 0; }