Browse Source

[Promise.object] Resolves all promises in nested, cyclic objects

tags/v0.10.0
Muthu Kumar 6 years ago
parent
commit
f1ae5aad20
  1. 117
      index.js

117
index.js

@ -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…
Cancel
Save