How to extract data out of a Promise
Asked Answered
U

6

54

I have a promise that returns data and I want to save that in variables. Is this impossible in JavaScript because of the async nature and do I need to use onResolve as a callback?

Can I somehow use this (e.g. wrap it with async/await):

const { foo, bar } = Promise.then(result => result.data, errorHandler);
// rest of script

instead of this?

Promise.then(result => {
   const { foo, bar } = result.data;
   // rest of script
 }, errorHandler);

Note: Bluebird library is used instead of native implementation, and I can't change from Promise to asnyc/await or Generators.

Unshakable answered 28/4, 2016 at 9:59 Comment(7)
It's possible with javascript. You can assign data you receive on promise success to variable which you have declared earlier. And your variable will have that value you received from promise. You can refer: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…Anstice
async/ await are coming!Collimore
@SandipNirmal This is possible but can cause bugs as JavaScript won't wait for the Promise to resolve. Also need to change const to let, which can have side effects.Mormon
Just FYI, errorHandler doesn't really handle an error here, it just executes immediatelyFranni
@RGraham This syntax is working fine with the bluebird.js implementation. See bluebird API reference here It's also correct according to Promises/A+ standard (see paragraph 2.2)Mormon
@TobiasMühl Unless errorHandler itself returns a function all that will happen here is that the return value of errorHandler will be passed to the rejectedHandler parameter. You'd need to pass errorHandler as a reference to the function: Promise.then(result => result.data, errorHandler);Franni
Possible duplicate of How to access the value of a promise?Unveiling
N
60

NO you can't get the data synchronously out of a promise like you suggest in your example. The data must be used within a callback function. Alternatively in functional programming style the promise data could be map()ed over.

If your are OK using async/await (you should it's awesome) then you can write code that looks synchronous yet retain the asynchronicity of a promise (see @loganfsmyth comments).

const { foo, bar }  = await iAmAPromise.then(result => result.data);

Overall since you are already using ES6 I assume you are also using a transpiler. In which case you should definitely give async/await a try. Just be sure to weight in the decision that as today they are not yet a ratified specification.

Nitin answered 28/4, 2016 at 10:5 Comment(11)
"it's awesome" Since when is blocking awesome?Eddins
@zeroflagL async isn't more blocking than promises callback. The thread is free to cary on processing other stuff (i.e. new requests if in NodeJS) while the execution will resume once the promise resolves. It's just a different syntax.Nitin
The execution of the function stops. It doesn't with Promise.then. So if you await a result in an event handler in a browser, it's definitely not awesome.Eddins
The downstream code in the handler wont execute but the thread will be freed up non the less. So browser wont freeze. Now if a function uses await at it's top level but the downstream code does other operation that do not requires the data returned by the async function then it's a design error.Nitin
So you think the user won't have a "freezing" experience if a scroll event or a key event takes seconds to be handled? I agree that await might be useful in a node server application or in a web worker, but not in front-end code. async is just sugar.Eddins
What makes the browser freeze is if the thread is blocked by synchronous operation. Which does not happen with async/await anymore than it does with a callback. Like you said it's just sugar but behind the scene it works out just the same.Nitin
You miss the point: The browser cannot continue scrolling as long as the scroll event handler has not finished its work. With await this can take a long time, with Promise.then not.Eddins
@zeroflagL You are incorrect, async/await does not block, it will execute the async function up to the first await and then suspend execution of the async function and return control to the parent scope and return a promise. When the value passed to await is available, the execution of the function will resume. There's an important difference between blocking and suspending execution.Teuton
@Teuton Shame on me. I just realized that I forgot one important point: You can only await within async functions. I had regular functions in mind, which would be synchronous and therefore would block.Eddins
Ahh, that'll do it :) The example in this answer was definitely lacking solid example usage, and people often forget that.Teuton
Right, you can't "await" globally, only inside the context of a function that can be paused and resumed (because this is all just sugar on top of generators). External to the function, you're still dealing with a Promise, and you can't get an eventual value "out" of a Promise in that context, you can only map/flatMap over it to do things with it.Sarver
S
19

While you can get a value from an awaited Promise inside an async function (simply because it pauses the function to await a result), you can't ever get a value directly "out" of a Promise and back into the same scope as the code that created the Promise itself.

That's because "out of" would mean trying to take something that exists in the future (the eventually resolved value) and putting it into a context (synchronous variable assignment) that already happened in the past.

That is, time-travel. And even if time-travel were possible, it probably wouldn't be a good coding practice because time travel can be very confusing.:)

In general, if you find yourself feeling like you need to do this, it's good sign that you need to refactor something. Note that what you're doing with "result => result.data" here:

Promise.then(result => result.data, errorHandler);
// rest of script

..is already a case of you working with (literally, mapping over) the value by passing it to a function. But, assuming that "// rest of script" does something important related to this value, you probably want to continue mapping over the now updated value with yet another function that then does something side-effect-y with the value (like display the data on the screen).

Promise
    .then(({ data }) => data)
    .then(data => doSomethingWithData(data))// rest of script
    .catch(errorHandler);

"doSomethingWithData" will be called (if it's ever called) at some unknown point in the future. Which is why it's a good practice to clearly encapsulate all that behavior into a specific function and then hook that function up to the Promise chain.

It's honestly better this way, because it requires you to clearly declare a particular sequence of events that will happen, explicitly separated out from the first run through all of your application code's execution.

To put it another way, imagine this scenario, hypothetically executed in the global, top-level scope:

const { foo, bar } = Promise.then(result => result.data, errorHandler);
console.log(foo);
//...more program

What would you expect to happen there? There are two possibilities, and both of them are bad.

  1. Your entire program would have to halt and wait for the Promise to execute before it could know what "foo" & "bar" would... nay, might be. (this is what "await," inside an async function, does in fact do: it pauses the entire function execution until the value is available or an the error is thrown)
  2. foo and bar would just be undefined (this is what actually happens), since, as executed synchronously, they'd just be non-existent properties of the top-level Promise object (which is not itself a "value," but rather a quasi-Monadic wrapper around getting an eventual value OR an error) which most likely doesn't even contain a value yet.
Sarver answered 29/4, 2016 at 19:39 Comment(6)
I am having the same problem. And I understand the asyncronous problem. What I am struggling to to get is how to make the sync functions wait for the async if I need an async function to use await and I didn't understand how to get a value out of a then to that a sync variable after waiting for the function.Ehrlich
You can't, really. Synchronous functions (and code in the global scope) don't/can't wait for async ones. Like in the original example, the key is: what's actually IN the "//rest of script" bit. What is the rest of the script going to be? If it's ever doing something with the variables foo and bar, then those will simply be undefined in the "rest" of the script because it's all going to execute synchronously, not wait for the Promise to return. And not only will they be undefined, but they will ALWAYS remain undefined, because that scope just runs and is completed in one single pass.Sarver
Ultimately, async/await changes the WAY code runs in a function: it makes the familiar step-by-step synchronous syntax work differently than it normally would. But that logic is ultimately just expressing the functionality of callbacks in a non-callback-based way of writing things. The win is that we get to use the same sort of syntax throughout, without as much nesting, or getting into higher level concepts like types. But it is confusing, for precisely this reason: it looks the same, but works according to a different logic.Sarver
I figure out that when I was trying to find out to do do what I wanted. I think a hard thing for us noobies to get is how to make these two kinds of functions to work together. For example I thought of using a global variable or a return value to use a value I needed from that async function while instead I had to the setState() function inside it.Ehrlich
Ah yep: if this is React you can think of setState as a sort of non-explicit callback. Everything in the script will run initially all at once, but then nothing will change or happen until some side-effect (like user input or a async request returning) triggers some additional actions, which then all happen in the context of the functions being called. It's definitely confusing because you CAN, in an async function, change variables set in the initial global scope. But you can't "get back out" to the original scope: it's done. It's in the past.Sarver
"it's a good practice to clearly encapsulate all that behavior into a specific function and then hook that function up to the Promise": this part helped me a lot to understand how to properly organize this type of code, thanks.Medallist
P
3
let out; mypromise.then(x => out = x); console.log(out)

Only use this code when

  • you are debugging by hand,
  • and you know the promise has already succeeded

Behaviour of this code:

  • While the promise has not resolved yet, out will be undefined.
  • Once the promise resolves to a failure, an error is thrown.
  • When the promise resolves to success, (which may be after the console.log), the value of out will change from undefined to the Promise result — maybe in the middle of what you were doing.

In production code, or really any code that runs without you, the code that uses the Promise result should be inside the .then() callback, and not use some out variable. That way:

  • your code won't be run too early (when the result is still undefined),
  • and won't run too late (because you don't need 'I think sleeping for 10 seconds should do it' workarounds),
  • and won't erroneously run when the promise fails.
Pirtle answered 17/2, 2023 at 15:32 Comment(0)
E
0

I have a solution of getting this value "out" if you will. This is a method at backend for uploading multiple files to AWS S3 which must be dealt asynchronously. I also need the responses from S3, so I need the values out of the Promise:

async function uploadMultipleFiles(files) {

const promises = [];    //Creating an array to store promises
for (i = 0; i < files.length; i++) {
    const fileStream = fs.createReadStream(files[i].path)

    const uploadParams = {
        Bucket: bucketName,
        Body: fileStream,
        Key: files[i].filename
    }

    promises.push(s3.upload(uploadParams).promise())  //pushing each promise instead 
                   //of awaiting, to enable for concurrent uploads.
}

await Promise.all(promises).then(values => {
    console.log("values: ", values) //just checking values
    result = values;  //storing in a different variable
});
return result; //returning that variable

}

The key lines in context with the issue being discussed here are these :

await Promise.all(promises).then(values => {
    console.log("values: ", values) //just checking values
    res = values;  //storing in a different variable
});
return res; //returning that variable

But of course we have to also await in the function that will be calling this :

const result = await uploadMultipleFiles(files);
Ellipticity answered 14/11, 2022 at 9:43 Comment(1)
The "but of course" here is the problem though. You're using an async function, which halts when it hits "await." That's a special feature in async functions though, not a way to get "out" of the context of callbacks. Note also that don't need the extra .then statement in your Promise.all line. If you're using await, then the result IS the array of results const res = Promise.all(promises), or even just return Promise.all(promises) works. But that's still, external to the function, going to be a Promise of an array of results.Sarver
F
0

All you need to do is to extract all you have in your promise by using a .then


yourFunction().then( resp => {
... do what you require here

let var1 = resp.var1;
let var2 = resp.var2;
...
.....
})

yourFunction() should return a Promise

Fetching answered 10/12, 2022 at 15:12 Comment(2)
This does not answer the question as it was asking about how to move the variables in the result out of the .then callback.Marler
You can't do that though. Think about what it means, in this case, to move a variable "out" of the context of a Promise and its callbacks. The global scope, the "outside" most people are thinking about just runs, line by line, all in a single thread, and then it's done. Outside the context of async and generator functions, it just runs to completion, without halting or hesitating for future values.Sarver
F
0

How to Get A Value From A Promise

YES! You can extract value out of a promise!

Do NOT let anyone here say you cannot. Just realize any variable that stores your returned promise value will likely have a short delay. So if you have a JavaScript script page that needs that data outside of the Promise or async-await functions, you may have to create loops, interval timers, or event listeners to wait to grab the value after some time. Because most async-await-promises are REST calls and very fast, that wait would require just a quick while loop!

It is easy! Just set a variable (or create a function) that can access the value inside your async or promise code and store the value in an outside variable, object, array, etc you can check on. Here is a primitive example:

    // I just created a simple global variable to store my promise message.
    var myDelayedData = '';

   // This function is only used to go get data.
   // Note I set the delay for 5 seconds below so you can test the delay
    const getData = () => {
      return new Promise((resolve, reject) => {
        setTimeout(() => resolve('my promise data'), 5000);
      });
    }

    // I like to create a second async function to get the data
    // from the promise object and save the data to my global variable.
    const processData = async () => {
       let data = await getData();
       // Save the delayed data to my global variable
       myDelayedData = data;
    }

    // Start the data call from the promise.
    processData();

    // Open up your browser, hit F12 to pull up the browser devtools
    // Click the "console" tab and watch the script print out
    // the value of the variable with empty message until after
    // 5 seconds the variable is assigned to the resolved promise
    // and apears in the message!

    // THAT IS IT! Your variable is assigned the promise value
    // after the delay I set above!

    // TEST: But let's test it and see...
    var end = setInterval(function(){
        console.log("My Result: " + myDelayedData);
        if(myDelayedData !== ''){
           clearInterval(end);
        }
    }, 1000);

    // You should see this in devtools console.
    // Each line below represents a 1 second delay.

    My Result: 
    My Result: 
    My Result: 
    My Result: my promise data

Most people seeing this code will say "Then why use a Promise, just make a call for the data, pause, and update your application?" True: The whole point of a Promise is to encapsulate data processes inside the promise and take actions while the rest of the script continues.

But... you may need to output a result outside the Promise. You may have other global processes that need that data because it changes the state of the global application, for example. But at least you know you can get to that Promise data if you needed it.

Flouncing answered 26/12, 2022 at 23:5 Comment(4)
So, the reason people say you cannot extract data out of a Promise is essentially to remind them that Promises happen in the future: they are essentially, syntactically, capturing the effect of a value only being available in the future. All you're doing here is setting a separate, parallel time machine that periodically checks a global value that's set within the Promise... but also in the future.Sarver
@Sarver - I agree with your views on Promises being a separate future event. But I challenge the idea that a Promise result should be inaccessible to other parts of your program. The result should be able to change application 'state', too. The future is not just a subjective linear event. Events impact your timeline but many other people, as well. :)Flouncing
It's absolutely true that you can create something to manage state, and store the results of Promise values in it. But this isn't what people mean by getting the value "out": what they want is what async functions provide: imperative-style extraction of a value into the same scope as the Promise. And that isn't possible, or even sensible, because outside of await/async functions, javascript evaluates line by line to completion, not waiting or pausing.Sarver
This is in contrast to a lot of languages people may be familiar with: ruby or php, for example, where certain operations in the standard libs won't execute the next line of code until they complete, however long they take.Sarver

© 2022 - 2024 — McMap. All rights reserved.