From f1ae5aad200cf382e1eee617e4e291af9f9c8938 Mon Sep 17 00:00:00 2001 From: Muthu Kumar Date: Sun, 3 Jun 2018 15:59:51 +0530 Subject: [PATCH] [Promise.object] Resolves all promises in nested, cyclic objects --- index.js | 117 +++++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 77 insertions(+), 40 deletions(-) diff --git a/index.js b/index.js index 5b43d01..fb1c187 100644 --- a/index.js +++ b/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.} 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; \ No newline at end of file