How to use JSON.stringify replace to await Promise values?

493 Views Asked by At

I have a JSON object I want to stringify. Some of the values in this object are Promises.

How can I use the replacer parameter in JSON.stringify() to pass the result of the Promise, instead of the promise itself?

To give a clear example of what I mean, let's assume the following object:

const data = {
    foo: "foo",
    get bar() {
        return Promise.resolve('bar')
    },
    get baz() {
        return 'baz'
    },
}

If I call JSON.stringify(data), the result is:

{"foo":"foo","bar":{},"baz":"baz"}

But I want "bar" to be bar (similar to the result of "baz").

A naive attempt, using a replacer with async / await, doesn't work:

const data = {
  foo: "foo",
  get bar() {
    return Promise.resolve('bar')
  },
  get baz() {
    return 'baz'
  },
}

const text = JSON.stringify(data, async(key, value) => {
  return value instanceof Promise ? await value : value
})

console.log(text)

So how do I get the value of the promise to be returned by the result of stringify?

2

There are 2 best solutions below

5
Salketer On BEST ANSWER

You'll need to deal with the promises first since the JSON.stringify method is synchronous.

async function awaitAllObjectPromises(obj) {
  const newObj = { ...obj
  };
  const proms = [];
  for (const k in newObj) {
    if (newObj[k] instanceof Promise) {
      proms.push(newObj[k].then(v => newObj[k] = v));
    } else if (typeof newObj[k] === 'object' && newObj[k] !== null) {
      proms.push(awaitAllObjectPromises(newObj[k]).then(v => newObj[k] = v));
    }
  }
  await Promise.all(proms);
  return newObj;
}
async function test() {
  const data = {
    foo: "foo",
    get bar() {
      return Promise.resolve('bar')
    },
    get baz() {
      return 'baz'
    },
  }
  console.log(JSON.stringify(await awaitAllObjectPromises(data)));
}
test();

The idea is to go through all the object properties and find promises. Add them to a list of promises to have them resolve in parallel instead of sequentially (small optimization for objects with lots of promises). We do this recursively to make sure we catch all promises.

Using then() we make sure the promises get replaced with their resolved value, and we await them all.

1
hxku On

This solution makes a recursive resolve of your Promises in object & then u may to stringify it

async function resolvePromiseJSON(json){
        const copy = Object.assign({}, json)
        for(const key of Object.keys(copy)){
            let value = copy[key]

        const isPromise = value instanceof Promise 
        if(isPromise) value = await value
        
        const isArray = value instanceof Array
        if(isArray) value = Object.values(resolvePromiseJSON(value))

        const isObject = value instanceof Object
        if(isObject && !isArray) value = resolvePromiseJSON(value)
       
        copy[key] = value
    }
    return copy
}

resolvePromiseJSON(data).then(JSON.stringify)