skip to Main Content

I’m looking at a functionality similar to p-props, p-all npm library but which recursively resolves promises

const values = {
    a: () => Promise.resolve(1),
    b: [() => Promise.resolve(2)],
    c: {
        d: () => Promise.resolve(3),
    },
};
console.log(await resolve(values, { concurrency: 2}));

What I’m expecting is {a: 1, b: [2], c: {d: 3}} and only 2 promises should be running at a time

I see that this can be accomplished by clubbing together p-all, p-props/p-map and their mappers. But not sure about concurrency. Is there any existing approach to such a requirement?

3

Answers


  1. Use the mapper(value, key) do this

    Receives the current value and key as parameters. If a value is a Promise, mapper will receive the value this Promise resolves to. Expected to return a Promise or value.

    import pProps from 'p-props';
    
    const values = {
      a: Promise.resolve(1),
      b: [Promise.resolve(2), Promise.resolve(2.1)],
      c: {
        d: Promise.resolve(3),
        e: {
          f: Promise.resolve(4),
          g: {
            h: [Promise.resolve(5), Promise.resolve(5.1)],
          },
        },
      },
    };
    
    const createMapper = (value, key) => {
      const mapper = (v, k) => {
        if (Array.isArray(v)) {
          return pProps(Object.fromEntries(v.map((e, i) => [i, e]))).then(
            Object.values
          );
        }
        if (Object.prototype.toString.call(v) === '[object Object]') {
          return pProps(v, mapper);
        }
        return v;
      };
      return mapper(value, key);
    };
    
    pProps(values, createMapper).then((r) => console.log(JSON.stringify(r)));
    

    Output:

    {"a":1,"b":[2,2.1],"c":{"d":3,"e":{"f":4,"g":{"h":[5,5.1]}}}}
    

    stackblitz

    Login or Signup to reply.
  2. This might work:

    async function recursiveResolve(input) {
    
      const result = await input;
      if (Array.isArray(result)) {
        return result.map(item => recursiveResolve(input));
      }
      if (typeof result === 'object') {
        return Object.fromEntries(Object.toEntries(result).map([k,v] => [k, recursiveResolve(v)]));
      }
      return result;
    
    }
    
    Login or Signup to reply.
  3. You could keep track of the pending promises (in a Set) and use Promise.race to detect when one of those resolves. Then you can execute the next task and add it again to that set, …etc.

    Use recursion to drill down to those tasks, and as promises resolve (using Promise.all), rebuild the object structure with the resolved values.

    A demo with promises that take varying times to resolve:

    async function resolveNested(tasks, {concurrency = 2}) {
        const pendingPromises = new Set;
        
        function enqueue(task) {
            if (pendingPromises.size < concurrency) { // We have room to create the promise now
                const promise = task().then(value => {
                    pendingPromises.delete(promise);
                    return value;
                });
                pendingPromises.add(promise);
                return promise;
            }
            // No room now. Try again when one pending promise has resolved
            return Promise.race([...pendingPromises]).then(() => enqueue(task));
        }
        
        function dfs(item) {
            if (typeof item === "function") return enqueue(item);
            // Apply recursion, and collect resolved values:
            const promise = Promise.all(Object.values(item).map(dfs));
            if (Array.isArray(item)) return promise;
            return promise.then(values => // Rebuild object structure
                Object.fromEntries(Object.keys(item).map((key, i) => [key, values[i]]))
            );
        }
    
        return dfs(tasks);
    }
    
    // Some helper functions for a demo:
    const elapsed = () => Math.floor((performance.now() - start) / 100) / 10 + " sec";
    
    const delay = async (ms, val) => {
        console.log(`${elapsed()} - Creating promise: delay(${ms}, ${val})`);
        await new Promise(resolve => setTimeout(resolve, ms));
        console.log(`${elapsed()} - Resolved promise: delay(${ms}, ${val})`);
        return val;
    };
    
    // Demo:
    const values = {
        a: () => delay(1000, "A"),
        b: [() => delay(2000, "b[0]"), () => delay(500, "b[1]")],
        c: {
            d: () => delay(3000, "c.d"),
            e: () => delay( 500, "c.e"),
        },
    };
    
    const start = performance.now();
    resolveNested(values, { concurrency: 2}).then(console.log);
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search