1

this is a problem that is going around for DAYS in my team:

We can't figure out how to save the result of a promise (after .then) in a variable.

Code will explain better what I mean:

We start with a simple async function that retrieves the first item of a list:

const helloWorld = () => [{ name: 'hi' }];

async function getFirstHelloWorld() {
  const helloWorldList = await helloWorld();
  return helloWorldList[0].name;
}

Now, we would like to call this function and save the content of aList:

let aList;

const a = async() => {
  aList = await getFirstHelloWorld()
}

a().then(() => {
    console.log('got result:', aList)
    console.log('aList is promise:', aList instanceof Promise)
});

console.log(aList)

aList called within the .then(), console.logs the right value.

aList called outside the .then(), console.logs Promise { }

How can we save returned values from promises to a variable?

  • 2
    There are several issues. First off, `helloWorld` is not an asynchronous function, thus you should not "await" it. Secondly, you expect a list for `a`, but only one item is returned (i.e. is not an array). Finally, `a` is already "awaited" (i.e. is not a `Promise`), so you don't have to call `then` or whatever: the result is `a` itself. – Mario Vernari Jul 29 '21 at 02:06
  • Can you try to return your function as a promise? – Tim Strawbridge Jul 29 '21 at 02:07
  • 1
    you seem to be attempting to synchronously get a value that comes from an asynchronous source - nothing can do this, no matter how many functions you put between your source and your destination, nothing can predict the future result of an asynchronous function - I find it amazing that in a **team** of programmers, not one understands asynchrony – Bravo Jul 29 '21 at 02:14

2 Answers2

1

You cannot take an asynchronously retrieved value, stuff it in a higher scoped variable and then try to use it synchronously. The value will not be present yet because the asynchronous result has not been retrieved yet. You're attempting to use the value in the variable before it has been set.

Please remember that await only suspends execution of a local function, it does not stop the caller from running. At the point you do an await, that function is suspended and the async function immediately returns a promise. So, NO amount of await or return gets you anything by a promise out of an async function. The actual value is NEVER returned directly. All async functions return a promise. The caller of that function then must use await or .then() on that promise to get the value.

Try running this code and pay detailed attention to the order of the log statements.

console.log("1");

const helloWorld = () => [{ name: 'hi' }];

async function getFirstHelloWorld() {
  const helloWorldList = await helloWorld();
  return helloWorldList[0].name;
}

let aList;

const a = async() => {
  console.log("beginning of a()");
  aList = await getFirstHelloWorld()
  console.log("end of a()");
}

console.log("2");

a().then(() => {
    console.log('got result:', aList)
    console.log('aList is promise:', aList instanceof Promise)
});

console.log("3");

console.log('attempting to use aList value');
console.log(aList)

console.log("4");

That will give you this output:

1
2
beginning of a()
3
attempting to use aList value
undefined
4
end of a()
got result: hi
aList is promise: false

Here you will notice that you are attempting to use the value of aList BEFORE a() has finished running and set the value. You simply can't do that in Javascript asynchronous code, whether you use await or .then().

And, remember that the return value from an async function becomes the resolved value of the promise that all async functions return. The value is not returned directly - even though the code syntax looks that way - that's a unique property of the way async functions work.

Instead, you MUST use the asynchronous value inside the .then() where you know the value is available or immediately after the await where you know the value is available. If you want to return it back to a caller, then you can return it from an async function, but the caller will have to use .then() or await to get the value out of the promise returned from the async function.

Assigning an asynchronously retrieved value to a higher scoped variable is nearly always a programming error in Javascript because nobody wanting to use that higher scoped variable will have any idea when the value is actually valid. Only code within the promise chain knows when the value is actually there.

Here are some other references on the topic:

Why do I need to await an async function when it is not supposedly returning a Promise?

Will async/await block a thread node.js

How to wait for a JavaScript Promise to resolve before resuming function?

Using resolved promise data synchronously

How to Write Your Code

So, hopefully it is clear that you cannot escape an asynchronous result. It can only be used in asynchronous-aware code. You cannot turn an asynchronously retrieved result into something you can use synchronously and you usually should NOT be stuffing an asynchronous result into a higher scoped variable because that will tempt people writing code in this module to attempt to use the variable BEFORE is is available. So, as such, you have to use the value inside a .then() handler who's resolved value has the value you want or after an await in the same function where the await is. No amount of nesting in more async functions let you escape this!

I've heard some people refer to this as asynchronous poison. Any asynchronous operation/value anywhere in a flow of code makes the entire thing asynchronous.

Here's a simplified version of your code that shows returning the value from an async function which will make it the resolved value of the promise the async function returns and then shows using .then() on that promise to get access to the actual value. It also shows using .catch() to catch any errors (which you shouldn't forget).

FYI, since this is not real asynchronous code (it's just synchronous stuff you've wrapped in some promises), it's hard to tell what the real end code should be. We would need to see where the actual asynchronous operation is rather than just this test code.

const helloWorld = () => [{ name: 'hi' }];

async function getFirstHelloWorld() {
  const helloWorldList = await helloWorld();
  return helloWorldList[0].name;
}

getFirstHelloWorld().then(name => {
    // use the name value here
    console.log(name);
}).catch(err => {
    console.log(err);
});
jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • Thanks for the detailed walk-through. What if you want to export the object? For instance: `export let aList`. – datalifenyc Aug 22 '22 at 16:18
  • @datalifenyc - I'm not sure what exact context you're asking this in. It may be that you should write your own question for the circumstance you're asking about. You cannot directly export an asynchronously retrieved value because the exports will be assigned before the asynchronous value is available. The reasoning is slightly different in a CommonJS module vs. an ESM module, but the result is the same. If you have an asynchronous value that you want someone to have access to, then you export a function they can call (that returns a promise) and the caller uses that promise to get the value. – jfriend00 Aug 22 '22 at 21:27
  • Thanks for the explanation. This explained it for me: `You cannot directly export an asynchronously retrieved value because the exports will be assigned before the asynchronous value is available.` – datalifenyc Sep 03 '22 at 20:55
0

You need to return the variable like this and use it within the .then callback.

const a = async() => {
  const res = await getFirstHelloWorld()
  return res
}

a().then((data) => {
    console.log('got result:', data)

});

Tim
  • 344
  • 1
  • 12
  • by the way `const a = async() => { const res = await getFirstHelloWorld(); return res;}` is just `const a = () => getFirstHelloWorld();` - the OP is trying desperately to get an asynchronous result synchronously, thinking that by using many functions along the way somehow the asynchrony will magically vanish :p – Bravo Jul 29 '21 at 02:15
  • Yea, but he had curly braces around it, it wasn't returning the value like your sample is doing. – Tim Jul 29 '21 at 02:19
  • I know, and so do you, but my point is that the `a` function is completely pointless - it's an attempt by the OP to "beat" asynchrony into submission :p - in his a function, he is setting the value of a global, thinking an async function works synchronously - because, that's what a lot of coders seem to believe! – Bravo Jul 29 '21 at 02:22