Access a variable outside of .then function [duplicate] - javascript

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 3 years ago.
I have some code that authenticates by posting an object using npm request.
After posting the JSON object, a JSON response is returned which contains an authn token I can use in future GET/POST request headers.
I have some async code that returns the correct authn token but I can only access it via the .then function code block.
I have read through the usual linked thread here: How do I return the response from an asynchronous call? but even though the return result is in the .then function I still get undefined when trying to do anything other than console.log().
const postData = {
"auth": {
"username": "username",
"password":"password"
}
};
var returnRequest = () => {
var options = {
method: 'POST',
url: 'https://api.appnexus.com/auth',
body: postData,
json: true
};
return new Promise(async (resolve, reject) => {
await requestAPI(options, function (error, response, body) {
if (error) {
reject(error);
}
resolve(body);
});
})
}
var returnedResult
returnRequest()
.then((result) => {
returnedResult = result.response.token
})
.catch((error) => {
console.log(error);
})
console.log(returnedResult)
I would expect to see the returnedResult store the token as I understand it, the .then promise only runs one the request has happened?
A developer said I have to build all subsequent code inside the .then block but that sounds crazy, that I have to have my whole program inside this returnRequest function rather than be able to pass the returned token back outside to a global variable?
Is that the correct way to do it, and am I supposed to just build all subsequent requests using the result.response.token inside the
returnRequest()
.then((result) => {
returnedResult = result.response.token
})
function?

.then is the mechanism that promises use to let you know when the value is available. The "when" part is important: only the promise object knows what time your code should run at. So even if you try to write some extra code to store values in variables, the question of when it's safe to try to get those variables can only be answered by the promise's .then method.
So yes, any code that needs the values to be available needs to be put in the .then of the promise. Maybe you have some separate part of the codebase that needs to interact with the result, and so it feels clumsy to try to have to copy that code over to here. Well you don't need to: you just need to pass that other code the promise, and then that other code can call .then on the promise itself. For example:
const tokenPromise = returnRequest()
.then(result => result.response.token);
// Anywhere else that tokenPromise in scope can write whatever code it needs to:
tokenPromise.then(token => {
// Do anything with the token
});
// And a completely different piece of code can write its own stuff with the token
tokenPromise.then(token => {
// Do other stuff with the token
});

No you don't need to use result.response.token everywhere to use the authn token.
The thing here to understand is the flow of code. Your console.log statement may be returning you undefined .
Why ? Haven't you updated the global variable inside the then block of promise ?
Yes you have ! But the problem is that it is not reflected to you in the console log statement because this very statement is executed before any updation in the global variable.
So, it gets updated but it takes time to do so.
This is what is known as asynchronous code .
Now what about the suggestion of wrapping the code inside the .then block.
If you will add a console log statement beneath the updation (inside the then block) it will print you the exact token you are looking for.
But actually you don't need that , you can use aysnc/ await syntax to make it look like synchronus code, and then it will don't confuse you.
For example you can do something like this.
let result = await returnRequest();
let returnedToken =result.response.token;
// Now it will print you the token
console.log(returnedToken)
Make sure to add the async keyword infront of the function using await.

there are several ways to do what you ask, one way would be to wrap your entire code in async iife (immediately invoked function expression) so that you can use await.
!async function(){
....
....
....
var returnedResult = await returnRequest()
.then((result) => {
return result.response.token;
})
.catch((error) => {
console.log(error);
})
//continue
}()

I’ll try and answer parts of this question.
The setting of value for global variable inside of the .then callback is correct and you’ll have the value inside the “then” block. You can console.log inside of it and check.
The console.log outside in the “global” scope runs even before the the promise is resolved. Remember that java script is even driven. It registers the api call and continues executing the next line of it can. Which is why you’ll see an undefined value of token.
If all your subsequent requests depend on the auth token and you need to call some other API in the same call, you’ll have to do it in the .then call or create a promise chain with multiple .then which is essentially the main benefit of Promises. Previous result is passed on to the next promise.

Related

Why does axios.get(url).data not work? (axios GET requests and property accessors; JavaScript)

I have an async function that is requesting an object with a name and id property. I understand that you can't return await axios.get(url).data.name without getting kicked into the catch statement.
My question though is what is the reason that it doesn't work?
async function getConstellationNameById(id) {
const url = `${BASE_URL}/constellations/${id}`;
try {
return await axios.get(url).data.name;
} catch (error) {
throw `Constellation with id of ${id} could not be found.`;
}
}
If I store the GET request in a variable instead and then use dot notation to access the name property it works:
try {
//return axios.get(url).data.name;
const constellation = await axios.get(url);
return constellation.data.name;
}
Is it because dot notation has a timing to it? Therefore, while the GET request is in the event queue, the .data.name portion is considered sync code and fails to get anything? I had thought since it's being executed all in one line, property accessors would execute along with the GET request?
Or is it because of another reason?
I also thought you could only access a Promise object's properties within .then() and .catch() statements; is that not the case?
Apologize in advance if I'm misusing any terminology as I'm still learning! Just really want to make sure I understand the reasoning before continuing.
. has higher precedence than await, so your code is equivalent to:
return await (axios.get(url).data.name);
so axios.get(url).data.name would need to be a Promise. But the promise that you want to await for is returned by axios.get(url). Add parentheses to force the proper grouping:
return (await axios.get(url)).data.name;

JavaScript returning a promise even though it prints a string to the console [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 2 years ago.
I'm trying to make a function that returns a string containing a jwt token, the function used in Amplify returns a promise and I can't get my head around promises but after some struggling, I've managed to get my function to get the string I need from the promise and print it to the console but when I then return this string from the function so I can call it from various places the resulting data is now a promise again. No idea what I'm doing wrong.
async function getToken() {
let userData = await Auth.currentAuthenticatedUser().then(result => result.signInUserSession).then(result => result.accessToken).then(result => result.jwtToken);
console.log(userData); // this prints the token perfectly as text to the console
return(userData); // I want this to return the token as a string not a promise
}
console.log(getToken(); // this prints a promise to the console again even though I've got it to a string in the function.
If you have used await inside a function than it will only return a Promise no matter what so you can just use
getToken().then(console.log)
// or
getToken().then(token => console.log(token))
// both are same
since you can not use await outside an async function as a matter of react application just use state such as update the state of application using setState() in the .then of the promise returned. there is no such need of making the function async.
or if you really want the component to be async than just study <Suspense> in react to handle the components that have to fetch data from a network before being displayed
use it like this.
let result = null
getToken().then(token => {
result = token
// Now you can use the result variable
console.log(result)
})
Think I've sussed it now thanks to #tfarmer4 and #Arish Khan. In my head I wanted to get the token as a string variable so I could pass it into my API call functions but I realise now I will need to call it from within each function so below is my example solution.
function getToken() {
return Auth.currentAuthenticatedUser().then(result => result.signInUserSession).then(result => result.accessToken).then(result => result.jwtToken);
}
function callAPI () {
getToken().then(data => {
let token = data;
console.log(token);
//more lines here such as calling my API using token as the variable of the jwt token
}
);
};
EDIT: Sandbox here All you need is this edit I believe. Remember that async functions are just promises. You have to do any work on the result by either setting the result to a variable with an let data = await Auth.currentAuthenticatedUser().then(result => result).then(data => data.jwtToken), or just do all the necessary work in the .then(data => {data.jwtToken //...your work here}).

Javascript - access variable in then outside it's scope [duplicate]

This question already has answers here:
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
(7 answers)
Closed 4 years ago.
I have the following Javascript and I am trying to access the content data out of the service scope, what's the best way to access that data?
Service
.getContent()
.then((content = {}) => {//access this content out of 'Service' scope})
.catch((error = {}) => {console.log('errror', error)})
I tried the following:
let data = null;
Service
.getContent()
.then((content = {}) => {data = content})
.catch((error = {}) => {console.log('errror', error)})
console.log(data);
But I get the error that data is undefined. How can I get the contents of content to data
You can't do it this way. The issue is that a .then() handler is called at some indeterminate time in the future. It's always called at least on the next tick of the event loop (per the promise specification) and if there's a real asynchronous operation behind the promise, then who knows when it will be called (it could be ms to hours to never).
The ONLY way you can possibly know when it gets called and thus when a value provided in the .then() handler is available is by using the data it provides inside the .then() handler itself or by calling some function from within the .then() handler and passing the data to it.
Your console.log(data) statement always runs BEFORE the .then() handler is called. That's why you can't use the variable there. And, the only place you actually know when the data is available is inside the .then() handler so that's where you need to consume the data.
This is a different way of thinking about coding. It's the asynchronous model that Javascript uses for lots of things and you do need to learn it in order to be successful with Javascript.
So, the proper way to do things is this:
Service.getContent().then((content = {}) => {
// use content here
console.log(content);
}).catch(err => {
// handle error here
console.log('errror', err)
});
FYI, ES7 allows you to use await to make your code "look" a little more synchronous. It's important to understand how the actual asynchronous model works, even when using await. But, in your example, you could do this:
async function someFunction() {
try {
let content = await Service.getContent();
// use content here
console.log(content);
return someValue;
} catch(e) {
// handle error here
console.log('errror', err)
return someOtherValue;
}
}
someFunction().then(val => {
// do something with val here
});
An important thing to understand is that while await appears to "block" the function execution until the promise resolves and makes it look like you can program synchronously again even with async operations, this is only partially true. The function execution itself is still asynchronous. In fact, as soon as you call await Service.getContent(), your function someFunction() returns a promise and any code after that function is called keeps on executing. So, the whole program flow is not blocked, only the internals of someFunction() are blocked waiting on that promise. await is really just syntactical sugar for .then(). The underlying concepts are still the same. And, functions still return immediately as soon as you await an asynchronous operation.
You can only use await inside an async function and all function declared async return a promise. So, the promise is still being used, await just gives you a bit more way to write code inside a function.

How to access values outside of axios request?

Very easy question. When I run console.log on the jsonPayload in line 6, I see the expected output. When I run console.log again on jsonPayload in the last line, it returns an empty {}. How do I access the payload outside of the initial request?
var jsonPayload = {}
axios.get('http://localhost:8080/api/tools')
.then(function (response) {
jsonPayload = response.data[0]
// this returns the expected payload
console.log(jsonPayload)
})
.catch(function (error) {
console.log(error)
})
// this returns empty {}
console.log(jsonPayload)
I am not sure why you would like to access the jsonPayload outside of the axios get call, but I do know why you are receiving a log of {} from the outside console.log().
axios.get()
The above method will return a promise. This promise allows you to protect your next process from receiving empty data.
Inside the .then() method, you are able to retrieve the data that is sent back to you from the axios.get() call. However, this data cannot reach you until the process of retrieving data from the API, is complete.
This is where you are running into your issue. The console.log() outside of the .then() method is triggering before the completion of the ajax call.
You could also look into using ES7 async and await. This allows you to write these axios calls without the .then().
You cannot do this because the request is processed asynchronously.
As #d3L answered, you're limited to handle the response within the callback passed to .then. However, after ES8 you can use async / await which is an alternative to using typical Promise handling with callbacks. Is still asynchronous, but looks synchronous:
(async function() {
try {
const response = await axios.get('http://localhost:8080/api/tools');
console.log(response.data[0])
} catch(err) {
console.log(err);
}
})();
Note whether you're running it on node or the browser, the engine might not support it, so you might need to transpile the code using babel.

How to extract data out of a Promise

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.
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.
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.
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)
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.
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.
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);
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
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.

Categories

Resources