Muthu Kumar
6 years ago
1 changed files with 77 additions and 40 deletions
@ -1,67 +1,104 @@ |
|||
"use strict"; |
|||
|
|||
const isObject = obj => obj && typeof obj === 'object' && obj.constructor === Object; |
|||
/** |
|||
* Returns true if x is an object, false otherwise. |
|||
* @param {any} x |
|||
* @returns {Boolean} |
|||
*/ |
|||
const isObject = x => x && |
|||
typeof x === 'object' && |
|||
x.constructor === Object; |
|||
|
|||
const makeCircular = (obj, path) => { |
|||
const start = (o, p, i = 1) => { |
|||
if (i === path.length) return o[p[0]] = obj; |
|||
if (p.length > 0) { |
|||
o[p[0]] = {}; |
|||
return start(o[p[0]], p.slice(1), i + 1); |
|||
} else return obj; |
|||
} |
|||
return start(obj, path); |
|||
}; |
|||
/* A well known Symbol. */ |
|||
const $SELF = typeof Symbol !== 'undefined' ? |
|||
Symbol('SELF') : |
|||
'[~~//-- SELF --//~~]'; |
|||
|
|||
const findPath = (obj, query) => { |
|||
return Object.keys(obj).reduce((acc, key) => { |
|||
/** |
|||
* Replaces values that match the query parameter |
|||
* with a reference to the parent parameter. |
|||
* @param {Object} object Object to make cyclic. |
|||
* @param {any} query Query to match against. |
|||
* @returns {Object} |
|||
*/ |
|||
const makeCyclic = (object, query) => { |
|||
const start = obj => Object.keys(obj).reduce((acc, key) => { |
|||
const value = obj[key]; |
|||
if (value === query) return [...acc, key]; |
|||
if (isObject(value)) return [...acc, ...findPath(value)]; |
|||
if (value === query) { |
|||
obj[key] = object; |
|||
return [...acc, key] |
|||
}; |
|||
if (isObject(value)) return [...acc, ...start(value, query)]; |
|||
else return acc; |
|||
}, []); |
|||
return start(object); |
|||
}; |
|||
|
|||
// Resolves array
|
|||
/** |
|||
* Promise.map polyfill. |
|||
* @param {Array.<any>} arr Array of Promises. |
|||
* @param {Function} functor Function to call resolved values. |
|||
*/ |
|||
const PromiseMap = (arr, functor) => |
|||
Promise.all(arr.map(x => Promise.resolve(x).then(functor))); |
|||
|
|||
// Resolves objects
|
|||
/** |
|||
* Resolve a flat object's promises. |
|||
* @param {Object} |
|||
* @returns {Object} |
|||
*/ |
|||
const ResolveObject = obj => |
|||
Promise.all( |
|||
Object.keys(obj).map(key => |
|||
Promise.resolve(obj[key]).then(val => obj[key] = val)) |
|||
Object |
|||
.keys(obj) |
|||
.map(key => |
|||
Promise |
|||
.resolve(obj[key]) |
|||
.then(val => obj[key] = val)) |
|||
) |
|||
.then(_ => obj); |
|||
|
|||
// Resolves recrusive deep objects
|
|||
const PromiseObject = parent => { |
|||
const ResolveDeepObject = object => |
|||
/** |
|||
* Recursively resolves deep objects with nested promises. |
|||
* @param {Object} object Object or value to resolve. |
|||
* @returns {Object} Resolved object. |
|||
*/ |
|||
const PromiseObject = object => { |
|||
let shouldReplaceSelf = false; |
|||
const ResolveDeepObject = obj => |
|||
Promise |
|||
.resolve(object) |
|||
.then(obj => { |
|||
if (Array.isArray(obj)) { |
|||
return PromiseMap(obj, obj => ResolveDeepObject(obj)); |
|||
} else if (isObject(obj)) { |
|||
.resolve(obj) |
|||
.then(resolvedObject => { |
|||
if (Array.isArray(resolvedObject)) { |
|||
// Promise and map every item to recursively deep resolve.
|
|||
return PromiseMap(resolvedObject, obj => ResolveDeepObject(obj)); |
|||
} else if (isObject(resolvedObject)) { |
|||
return ResolveObject( |
|||
Object |
|||
.keys(obj) |
|||
.reduce((acc, key) => |
|||
(obj[key] === parent) ? { |
|||
.keys(resolvedObject) |
|||
.reduce((acc, key) => { |
|||
if (resolvedObject[key] === object) { |
|||
shouldReplaceSelf = true; |
|||
return { |
|||
...acc, |
|||
[key]: $SELF, // Replace with resolved object.
|
|||
} |
|||
} |
|||
return { |
|||
...acc, |
|||
[key]: '[[ SELF ]]', |
|||
} : { |
|||
...acc, |
|||
[key]: ResolveDeepObject(obj[key]), |
|||
}, {})); |
|||
[key]: ResolveDeepObject(resolvedObject[key]), |
|||
} |
|||
}, {})); |
|||
|
|||
} |
|||
return obj; |
|||
return resolvedObject; |
|||
}); |
|||
return ResolveDeepObject(parent) |
|||
return ResolveDeepObject(object) |
|||
.then(obj => { |
|||
const path = findPath(obj, '[[ SELF ]]'); |
|||
return path.length > 0 ? makeCircular(obj, path) : obj; |
|||
}) |
|||
// Replace $SELF with reference to obj
|
|||
if(shouldReplaceSelf) makeCyclic(obj, $SELF); |
|||
return obj; |
|||
}); |
|||
}; |
|||
|
|||
module.exports = PromiseObject; |
Loading…
Reference in new issue