Javascript - access variable in then outside it's scope [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 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.

Related

Why is async required to call await inside a JavaScript function body? [duplicate]

This question already has answers here:
Why do I have to put async keyword to functions which have await keywords?
(1 answer)
JS async/await - why does await need async?
(3 answers)
Why 'await' requires 'async' in function definition
(4 answers)
Nodejs why is await only restricted to async functions?
(3 answers)
ES2017 - Async vs. Yield
(4 answers)
Closed 2 years ago.
One has to use async keyword on the containing function to use the await inside the function body.
async function fetchMovies() {
const response = await fetch('/movies');
console.log(response);
}
fetchMovies();
The await is being used to block on completion of the asynchronous fetch() call. As can be seen in the code, the function fetchMovies() is not even returning any value. And even if it did, it would affect the way the return value is consumed by the caller, but why should it matter to the call to another asynchronous call from the function body?
My question is why is this required? Is there some good explaination of that? Is it related to the need for actual implementation of await and supporting it in older JavaScript versions?
I know iffi pattern is used be able to use await but does that change the semantics for the code that follows the iffi code block in any way?
(async () => {
const response = await fetch('/movies');
console.log(response);
})();
I am also aware of top level await being supported in modules.
May be I am missing something really obvious.
There are three reasons the async keyword exists:
In ECMAScript language versions prior to 2015, await was not a keyword. Marking a function async provides a syntactic "bailout" to indicate a breaking change in the language grammar within the body of the function.
This is the most important reason. Without the async keyword, all programs written in ECMAScript 5 or older would no longer work if they used the await keyword as a variable (in fact this was done intentionally in some cases as a polyfill before async/await was standardized), since that would cause a breaking change without the addition of async to the specification. Because of this, async is syntactically necessary to avoid breaking changes to the language.
It provides a convenient marker for parsers, avoiding an infinite look-ahead in order to determine whether or not a function is asynchronous.
This makes parsing more efficient, which is appealing for both ECMAScript implementers and developers, though this reason alone does not make async strictly necessary to the syntax.
async also performs its own transformation on the function, which is done regardless of whether or not the await keyword is present in the body.
Consider the following two functions:
function foo() {
if (Math.random() < 0.5) {
return 'return';
} else {
throw 'throw';
}
}
async function bar() {
if (Math.random() < 0.5) {
return 'return';
} else {
throw 'throw';
}
}
async performs the following transformation of function bar():
function bar() {
return new Promise((resolve, reject) => {
try {
resolve((/*async function bar*/() => {
if (Math.random() < 0.5) {
return 'return';
} else {
throw 'throw';
}
})());
} catch (reason) {
reject(reason);
}
});
}
Those familiar with promises will recognize that we can simplify the above since the Promise constructor executor function will implicitly reject if it throws an error synchronously:
function bar() {
return new Promise((resolve) => {
if (Math.random() < 0.5) {
return resolve('return');
} else {
throw 'throw';
}
});
}
There are two questions being asked here. Why do you need the async keyword to indicate an async context, and why do you need async context to use await?
If we didn't use the async keyword to indicate an async context, then we would have backwards compatibility issues. Before the async/await update, "await" was a valid variable name. So to make it a reserved word, we need to introduce a new context, the async function context.
The second question is, why do we want this in the first place? Why do we want to differentiate async functions from traditional synchronous code? As the name implies, async functions do not execute all their code at once. When a function "awaits", it effectively stops execution, and the remainder of the code becomes an event handler for the thing being awaited. This allows other code to run.
Browser javascript implementations are single-threaded. Only one thing can be performed at a time, but the event queue allows functions to take turns. Consider what would happen if you could await from a synchronous function. The synchronous code, by definition, does not give up control. Therefore, the async function it's waiting for will never get a chance to swap in to your single execution thread, and you will be waiting forever. So, you can only await an async function if you are already in an async context.
I assume your exact question is this: "Handling return values (null or something) depends on the consumer who called the function. They "should" supposedly get it even if another asynchronous function is called in-between. So why does it matter to wait before further other asynchronous calls?"
You see, such fetch() calls are done in Databases within the duration of "initiating" and "closing" the connection. Almost every method used is asynchronous in this case. So while you're executing fetchMovies(); The thread might move further and execute connection.close(); before the fetching is resolved and returned.
The exact scenarios are like this:
await client.connect(); //connection establishment
// Your purposeful methods
async function fetchMovies() {
const response = await fetch('/movies');
console.log(response);
}
await fetchMovies();
// Closing connection to avoid attacks and maintain concurrency
await client.close();
If any method, in this case, is called in an asynchronous manner, the whole session of a Database connection is wasted and our function would return undefined or throw an error "Cannot use a session that has ended"
So we need to "wait" for the "Pending" Promises to reach a "Fulfilled" or "Rejected" state before executing further calls.
You can refer more to this article: Using Promises, async / await with MongoDB just for the sake of understanding.
I think it's to make it clear that the function contains asynchronous code. Let's use another example that does return something:
async function canUseGeolocation() {
const result = await navigator.permissions.query({name: 'geolocation'});
return result.state;
}
The async keyword tells the javascript engine that the function should return a promise, and any return statements in the function should resolve that promise. What happens if we modify it to cache the values so we don't always call the await?
function canUseGeolocation() {
if (cachedPermissionState) return cachedPermissionState;
const result = await navigator.permissions.query({name: 'geolocation'});
cachedPermissionState = result.state;
return result.state;
}
How should javascript know that the function should return a promise? Because it contains an await? What if you later change the function so that the cachedPermissionState is set elsewhere and this function only returns that value so you remove the await? Should that first return statement return a promise or return the value? That would now change the whole way the function is executed and what is returned by return cachedPermissionState;. By using async, we can know that it really returns a promise that the return statement resolves without scanning the function for await statements to determine if it should be treated as async or not.

Access a variable outside of .then function [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 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.

Firing Vuex actions asynchronously and sequentially - what am I not understanding?

I have a Vuex store and I am trying to fetch data from the Firebase Realtime Database. I am initially fetching the user information, however afterwards I would like to fetch some other information that relies upon the initial data fetched.
As you can see from the code, I am trying to do this using async / await, however whenever firing the two actions in my created() hook, the user's information isn't initialised, and therefore the second action fails.
My user store
async fetchCreds({ commit }) {
try {
firebase.auth().onAuthStateChanged(async function(user) {
const { uid } = user
const userDoc = await users.doc(uid).get()
return commit('SET_USER', userDoc.data())
})
} catch (error) {
console.log(error)
commit('SET_USER', {})
}
}
My club action which relies upon the above call
async fetchClubInformation({ commit, rootState }) {
try {
const clubIDForLoggedInUser = rootState.user.clubId
const clubDoc = await clubs.doc(clubIDForLoggedInUser).get()
return commit('SET_CLUB_INFO', clubDoc.data())
} catch (error) {
console.log(error)
}
}
}
The methods being called within my component's created() method.
created: async function() {
await this.fetchCreds();
await this.fetchClubInformation();
this.loading = false;
}
I have a feeling I'm fundamentally misunderstanding async / await, but I can't understand what in the code is incorrect - any help or advice would be greatly appreciated.
I'm not particularly familiar with Firebase but after a bit of digging through the source code I think I can shed a little light on your problems.
Firstly, consider the following example:
async function myFn (obj) {
obj.method(function () {
console.log('here 1')
})
console.log('here 2')
}
await myFn(x)
console.log('here 3')
Question: What order will you see the log messages?
Well here 2 will definitely come before here 3 but it's impossible to tell from the code above when here 1 will show up. It depends on what obj.method does with the function it's been passed. It might never call it at all. It might call it synchronously (e.g. Array's forEach method), in which case here 1 will appear before the other messages. If it's asynchronous (e.g. timers, server calls) then here 1 may not show up for some time, long after here 3.
The async modifier will implicitly return a Promise from the function if it doesn't return a Promise itself. The resolved value of that Promise will be the value returned from the function and the Promise will resolve at the point the function returns. For a function without a return at the end that's equivalent to it finishing with return undefined.
So, to stress the key point, the Promise returned by an async function will only wait until that function returns.
The method onAuthStateChanged calls its callback asynchronously, so the code in that callback won't run until after the surrounding function has completed. There's nothing to tell the implicitly returned Promise to wait for that callback to be invoked. The await inside the callback is irrelevant as that function hasn't even been called yet.
Firebase makes extensive use of Promises, so typically the solution would just be to return or await the relevant Promise:
// Note: This WON'T work, explanation follows
return firebase.auth().onAuthStateChanged(async function(user) {
// Note: This WON'T work, explanation follows
await firebase.auth().onAuthStateChanged(async function(user) {
This won't work here because onAuthStateChanged doesn't actually return a Promise, it returns an unsubscribe function.
You could, of course, create a new Promise yourself and 'fix' it that way. However, creating new Promises using new Promise is generally considered a code smell. Typically it's only necessary when wrapping code that doesn't support Promises properly. If we're working with a library that has proper Promise support (as we are here) then we shouldn't need to create any Promises.
So why doesn't onAuthStateChanged return a Promise?
Because it's a way of watching all sign-in/sign-out events. Every time the user signs in or signs out it'll call the callback. It isn't intended as a way to watch a particular sign-in. A Promise can only be resolved once, to a single value. So while a single sign-in event could be modelled with a Promise it's meaningless when watching all sign-in/sign-out events.
So fetchCreds is registering to be notified about all sign-in/sign-out events. It doesn't do anything with the returned unsubscribe function, so presumably it'll be listening to all such events until the page is reloaded. If you call fetchCreds multiple times it'll keep adding more and more listeners.
If you're waiting for a user to finish signing in then I suggest waiting for that directly instead. firebase.auth() has various methods starting with the prefix signIn, e.g. signInWithEmailAndPassword, and these do return a Promise that resolves when the user has finished signing in. The resolved value provides access to various information, including the user. I don't know which method you're using but the idea is much the same for all of them.
However, it might be that you're really just interested in grabbing the details of the current user. If that's all you want then you don't need to use onAuthStateChanged at all. You should just be able to grab a copy using the currentUser property. Something like this:
async fetchCreds({ commit }) {
try {
const { uid } = firebase.auth().currentUser
const userDoc = await users.doc(uid).get()
commit('SET_USER', userDoc.data())
} catch (error) {
console.log(error)
commit('SET_USER', {})
}
}
As I've already mentioned, this relies on the assumption that the user is already signed in. If that isn't a safe assumption then you might want to consider waiting until after sign in has completed before creating components that need user credentials.
Update:
Questions from the comments:
If the obj.method() call was asynchronous and we did await the callback function within it, would that ensure that the outer async function (myFn) never resolves before the inner one has finished?
I'm not entirely sure what you're asking here.
Just to be clear, I'm being very careful with my use of the words async and asynchronous. A function such as setTimeout would be considered asynchronous but it is not async.
async/await is just a lot of syntactic sugar around Promises. You don't really wait for a function, you wait for a Promise. When we talk about awaiting an async function we're really talking about waiting for the Promise it returns to resolve.
So when you say await the callback function it's not really clear what that means. Which Promise are you trying to await?
Putting the async modifier on a function doesn't make it magically wait for things. It will only wait when it encounters await. You can still have other asynchronous calls within an async function and, just like with a normal function, these calls will be performed after the function has returned. The only way to 'pause' is to await a Promise.
Putting an await inside another function, even a nested function, won't make any difference to whether the outer function waits unless the outer function is already waiting for the inner function. Behind the scenes this is all just Promises chaining then calls. Whenever you write await you're just adding another then call to a Promise. However, that won't have the desired effect unless that Promise is in the same chain as the Promise returned by the outer async function. It only needs one link to be missing for the chain to fail.
So modifying my earlier example:
async function myFn (obj) {
await obj.method(async function () {
await somePromise
// ...
})
// ...
}
await myFn(x)
Note that there are 3 functions here: myFn, method and the callback passed to method. The question is, will await myFn(x) wait for somePromise?
From the code above we can't actually tell. It would depend on what method does internally. For example, if method looked like this then it still wouldn't work:
function method (callback) {
setTimeout(callback, 1000)
}
Putting async on method won't help, that'll just make it return a Promise but the Promise still won't be waiting for the timer to fire.
Our Promise chain has a broken link. myFn and the callback are both creating their parts of the chain but unless method links those Promises together it won't work.
On the other hand, if method is written to return a suitable Promise that waits for the callback to complete then we will get our target behaviour:
function method (callback) {
return someServerCallThatReturnsAPromise().then(callback)
}
We could have used async/await here instead but there was no need as we can just return the Promise directly.
Also, if in the async myFn function you're not returning anything, does that mean it'll resolve immediately and as undefined?
The term immediately is not well-defined here.
If a function isn't returning anything at the end then it's equivalent to having return undefined at the end.
The Promise returned by an async function will resolve at the point the function returns.
The resolved value for the Promise will be the value returned.
So if you aren't returning anything it will resolve to undefined. Resolving won't happen until the end of the function is reached. If the function doesn't contain any await calls then this will happen 'immediately' in the same sense as a synchronous function returning 'immediately'.
However, await is just syntactic sugar around a then call, and then calls are always asynchronous. So while the Promise might resolve 'immediately' the await still has to wait. It's a very short wait, but it isn't synchronous and other code may get the opportunity to run in the meantime.
Consider the following:
const myFn = async function () {
console.log('here 3')
}
console.log('here 1')
Promise.resolve('hi').then(() => {
console.log('here 4')
})
console.log('here 2')
await myFn()
console.log('here 5')
The log messages will appear in the order they're numbered. So even though myFn resolves 'immediately' you'll still get here 4 jumping in between here 3 and here 5.
To make it short
fetchCreds({ commit }) {
return new Promise((resolve, reject) => {
try {
firebase.auth().onAuthStateChanged(async function(user) {
const { uid } = user
const userDoc = await users.doc(uid).get()
commit('SET_USER', userDoc.data())
resolve()
})
} catch (error) {
console.log(error)
commit('SET_USER', {})
resolve()
}}
}
async () => undefined // returns Promise<undefined> -> undefined resolves immediatly
asnyc () => func(cb) // returns Promise<any> resolves before callback got called
() => new Promise(resolve => func(() => resolve())) // resolves after callback got called

Is there a practical advantage to returning a pending promise? [duplicate]

This question already has answers here:
Difference between `return await promise` and `return promise`
(7 answers)
Closed 4 years ago.
For the most part, I think I understand how async functions work in JavaScript / Node.js, and I am familiar with the syntax of async/await. But this question is about a very specific case, which I can’t seem to find anywhere, and I think it involves the inner workings of await, which is beyond my understanding.
First I’ll start off with a simple example, and then I’ll extend it to real working code. (Note: I’m actually using Typescript, so you’ll see void instead of undefined, but that’s not important to the problem.)
async function subtask() {
// doSomeStuff();
return; // returns a `Promise<void>`
}
// async function run() {
// see options below
// }
function app() {
run().catch((e) => console.error(e));
}
Option 1
async function run() {
await subtask(); // resolves to `void`
return; // returns a new `Promise<void>`
}
Option 2
async function run() {
return subtask(); // returns a new Promise, resolving to the `Promise<void>` of `subtask()`
}
In the simple example above, I have an async function run that calls a smaller async function subtask. Both functions must return a Promise<void>. I have two options: (1) to await the smaller function and return a new Promise<void>, or (2) to return a wrapped promise, given by the smaller function, which will later resolve to void.
My lack of understanding is about how this works. In Option 1, is execution is paused before subtask() returns? What does that actually mean? Does that mean that the async subtask executes synchronously? Does that mean that app, which calls run(), will also pause execution? What if app was async, would that make a difference?
Is it “better” (more performant) to let the promise bubble up and resolve later, or to resolve it directly within the run function?
The reason this is important is that in my real code I have a bunch of smaller subtasks, which all return void, and then the big function must also return void—it cannot return an array. (Note that the subtasks need not run in any particular order.)
Option 1
async function run() {
await Promise.all([
subtask0(),
subtask1(),
subtask2(),
]);
return;
}
Option 2
async function run() {
return Promise.all([
subtask0(),
subtask1(),
subtask2(),
]).then((_all) => {});
}
function app() {
// do some stuff
run(); // if `run` contains `await`, does execution pause here?
// what if `app` was async?
// do some more stuff
}
It is always better to let the Promise bubble up. This avoids the creation of an extra Promise object that will also be awaited on (whether this is optimized away behind the scenes in whichever JavaScript engine you're using is up to debate, though).
async function run() {
await subtask();
return;
}
This creates an extra Promise (and subsequently an extra callback in the chain that will be executed).
async function run() {
return subtask();
}
This isn't actually doing what you think it is. This is also creating an extra Promise (since you're using the async keyword) and is almost functionally the same as the previous example. By using the async keyword, you're creating and returning a new Promise that will resolve/reject to the same value as the Promise created from the call to subtask(). If you remove the async keyword, then this will avoid the creation of the extra unnecessary Promise .
Now with your Promise.all() examples, I think they're both optimal (assuming you remove the unnecessary async keyword from the 2nd one, like noted above). The first would create 2 Promise s (the Promise.all() one, and the one created from returning from an async function), and the 2nd would as well (the Promise.all() one, and the one created from calling then()). Whichever you'd want to use is basically up to personal choice. Personally, I like the 2nd example since it's not mixing the use of async functions and Promises, which I think makes it a little easier to understand.
And for the last part of your question, execution will be paused wherever the await keyword exists (so within the run() call).
You can also essentially think of the await keyword transforming your code from this:
// Do some stuff
let result = await run();
// Do some other stuff
to this:
// Do some stuff
run().then(result => {
// Do some other stuff
};
(the whole point of await is to reduce confusion/complexity caused by nesting)

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