Optimal Syntax for object creation requiring async calls in Javascript

Viewed 62

I have an object that has properties that are generated via async functions. I feel like it is calling one, then calling the next, etc. I don't need it to do that but I do need it all be complete before moving on. Below is how I currently do it but I am looking for a more efficient method. I have thought about calling all the functions using Promise.All() in variables. Then creating the object and setting the properties to the variables but I have a feeling someone has a more elegant solution somewhere.

let obj = {
prop1 : await functionName(something),
prop2 : await anotherFunction(somethingElse)}
2 Answers

Interesting question. I was thinking and come up with 2 solutions.

The 1st one is by using Promise.all as you mentioned:

function delay(ms = 0) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

async function functionName() {
  await delay(1000)
  return 'value1'
}

async function anotherFunction() {
  await delay(500)
  return 'value2'
}

async function createAsyncObject() {
  const obj = {}

  // Set properties

  await Promise.all([
    functionName().then(v => obj.prop1 = v),
    anotherFunction().then(v => obj.prop2 = v)
  ])

  return obj
}

const obj = await createAsyncObject()

The 2nd one I make use of Proxy object to make it handle the Promises to set the object's properties:

function delay(ms = 0) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

async function functionName() {
  await delay(1000)
  return 'value1'
}

async function anotherFunction() {
  await delay(500)
  return 'value2'
}

function asyncObjectWrapper(obj) {
  const promises = []
  const then = function then(callback) {
    return Promise.all(promises).then(() => callback())
  }

  return new Proxy(obj, {
    get: (target, property, receiver) => {
      if (property !== 'then') return

      return then
    },
    set: (target, property, value) => {
      if (!(value instanceof Promise)) return true

      promises.push(value)

      value.then(v => target[property] = v)

      return true
    }
  })
}

async function createAsyncObject() {
  const obj = {}
  const wrapper = asyncObjectWrapper(obj)

  // Set properties

  await Object.assign(wrapper, {
    prop1: functionName(),
    prop2: anotherFunction()
  })

  return obj
}

const obj = await createAsyncObject()

You could create a list of name, value pairs by using Promise.all() and your property retrieval functions.

Once you have this list of entries you can use Object.fromEntries() to create your new object.

In this example, the property retrieval functions are getA(), getB() and getC(), they could, of course be called anything.

function getA() {
    return new Promise(resolve => setTimeout(resolve, 500, 'value a'))
}

function getB() {
    return new Promise(resolve => setTimeout(resolve, 500, 'value b'))
}

function getC() {
    return new Promise(resolve => setTimeout(resolve, 500, 'value c'))
}

async function getProperty(name, fn) {
    let value = await fn();
    return [name, value];
}

async function getObj() {
    console.log('Getting properties...');
    const entries = await Promise.all([ getProperty('a', getA), getProperty('b', getB), getProperty('c', getC) ]);
    console.log('Properties:', entries);

    const newObj = Object.fromEntries(entries);
    console.log('New obj:', newObj)
}

getObj()
.as-console-wrapper { max-height: 100% !important; }

Related