I am doing the following:
fetch("someurl")
.then(data => {return data.json()})
.then(resp => console.log(resp));
Now, usually i do the operations on resp from within the .then function, but would it be possible to assign resp to a variable, or at least store it somewhere so i can retrieve it in another function?
Example:
let thedata;
fetch(URL).then(res => {
return res.json()
}).then(data => {
console.log(data[0].category); //helloWorld
thedata = data[0].category
});
console.log(thedata);
function printsomething()
{return thedata}
Now thedata is going to be undefined, and i can't use the function printsomething without having it inside the .then() function.
This is what i meant by my question.
By assigning the fetch Promise chain to a variable, you can then call .then on that variable from multiple locations, though that's somewhat odd to do:
const prom = fetch("someurl")
.then(res => res.json());
prom.then((data) => {
console.log(data);
});
// Separately:
prom.then((data) => {
console.log(data.foo);
});
In most cases, it would make more sense to use your original strategy of putting everything inside a single .then after res.json().
Sure you can do that - but then you will need to track whether the async function has returned a value, which may be as simple a checking whether the value is defined. The variable could be in global scope, or a field in some object you import. But by not having the code that requires it called from within the then, it means wrapping use of that variable in a check, and handling the possibility it hasn't been set yet. Whether or not that is a good idea depends on context - best to avoid it if you can, but it's a pattern I have used on occasion.
No matter how you handle it you will need to either check or wait for the value so it's best to use it within the .then()
While it can be done if you need the variable outside of the .then() I think async/await is cleaner/easier to manage the flow by awaiting functions till they finish in the same way that .then would and ensuring your variable is available (you should still validate):
const someAPI = "https://jsonplaceholder.typicode.com/todos/1"
let data = null
const fetchFunc = async () =>{
const response = await fetch(someAPI)
data = await response.json()
// use data
}
const asyncFunc = async () => {
await fetchFunc()
console.log(data);
//use data
}
asyncFunc()
Related
I have the following endpoint in a class called UserApi.js:
const controller = 'User';
...
export async function getEmployeeInfo(employeeId)
{
const query = createQueryFromObject({employeId});
const response = await get(`/${controller}/EmployeeInfo?${query}`);
return retrieveResponseData(response, []);
}
This is going to get the required information from an action method in the backend of UserController.cs.
Now, say that I want to display this information in EmployeeView.vue class, do I have to await it again? Why or why not? Initially, I would say no, you don't, as you already dealt with the await/async in the UserApi.js class, but what about the Promise.resolve? Please explain.
methods: {
async setReportData(
employeeId
) {
this.isBusy = true;
Promise.resolve(getEmployeeInfo(
employeeId
)).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Update:
....
* #param {Object} response
* #param {any} defaultData
* #param {Function} predicate
* #returns {Promise}
*/
export function retrieveResponseData(response, defaultData = null, predicate = (predicateResponse) => predicateResponse) {
const data = predicate(response) ? response.data : null;
return data || defaultData;
}
You need to await it since a function declared with async keyword ALWAYS returns a Promise, even if you do only synchronous stuff inside of it, hence you need to await or "thenize" it to access the value it resolved to. That depends from the implementation details of async functions which are just generators that yield promises.
If this concerns you because you work inside sync modules and don't like to use async functions just to execute more async functions, there's a good news, TOP-LEVEL await MODULES proposal is at stage 4, so it'll very soon be shipped with the next ECMA version. This way you will be able to await inside modules as if they were wrapped by async functions !
https://github.com/tc39/proposal-top-level-await
I can't tell if you need to await it again, because I can't tell what retrieveResponseData does. It might take the resolved value and wrap it in a fresh promise, which would then require callers of getEmployeeInfo to await the result.
Here's the why:
A Promise is a wrapper around a value
await unwraps a Promise. So does the .then() handler you can register with a Promise (but the value is only unwrapped within the function you provide to .then()).
Just like a gift in the real world, once something has been unwrapped, you don't need to unwrap it again. However, very conveniently for us, you can still use await on a value that is not wrapped in a Promise, and it will just give you the value.
You can wrap any value in a Promise, like so:
let wrappedFive = Promise.resolve(5)
//> wrappedFive is a Promise that must be unwrapped to access the 5 inside it
// this does _exactly_ the same thing as the above
let wrappedFive = new Promise(resolve => {
resolve(5)
})
Sometimes you end up in a situation where you can't use await, because you're in a function that cannot be marked async. The lifecycle methods of front-end frameworks like React (and possibly Vue) are like that: the framework needs each lifecycle method to do its job and be done immediately. If you mark the lifecycle method as async, you can often prevent it from having the intended effect.
In cases like that, you often need to use chained .then() handlers, which is a little uglier, but it works:
componentDidMount() {
// this API call is triggered immediately by lifecycle,
// but it basically starts a separate thread -- the rest
// of this function does not wait for the call to finish
API.getUserInfo()
.then(userInfo => {
// this happens after the API call finishes, but
// componentDidMount has already finished, so what happens
// in here cannot affect that function
this.setState({ username: userInfo.username })
})
// this happens immediately after the API call is triggered,
// even if the call takes 30 seconds
return 5
}
Note that a Promise does not actually start a separate thread -- these all happen in the same thread that executes the lifecycle method, i.e. the browser's renderer thread. But if you think of the codepath that executes, a Promise that you don't wait for basically introduces a fork into that codepath: one path is followed immediately, and the other path is followed whenever the Promise resolves. Since browserland is pretty much a single-threaded context, it doesn't really hurt you to think of creating a Promise as spawning a separate thread. This is a nuance you can ignore until you are comfortable with asychronous patterns in JS.
Update: retrieveResponseData does not appear to return a Promise. I could be wrong, if predict returns a Promise, but if that were true, then the ternary would always return response.data because unwrapped Promises are truthy values (even Promise.resolve(false) is truthy).
However, anyone who calls getEmployeeInfo will have to wait it, because that function is marked async, and that means it returns a Promise even if nothing inside it is is asynchronous. Consider this extreme example:
// this function returns a number
function gimmeFive() {
return 5
}
// this function returns a Promise wrapped around a number
async function gimmeFive() {
return 5
}
Async function getEmployeeInfo awaits the result of the get call in order to return the value returned by a call to retrieveResponeData.
Assuming neither get nor retrieveResponeData errors, the value syntactically returned in the body of getEmployeeInfo is used to resolve the promise object actually returned by calling getEmployeeInfo.
Promise.resolve is not needed to convert the result of calling getEmployeeInfo into a promise because, given async functions return promises, it already is.
It doesn't matter if retrieveResponseData returns a promise or not: standard async function processing waits for a returned promise value to be settled before resolving the promise returned when calling the async function.
Async function setRreportData is declared as async but written using chained promise handler methods to process data and error conditions in order - the async declaration could be omitted, but may be useful if modifications are made.
The results can only be used to update the page at a time when the data has finished being obtained and extracted, shown as a comment in the following code:
setReportData( employeeId) {
this.isBusy = true;
getEmployeeInfo(
employeeId
).then((resultsEmployeeInfo) => {
this.reportDatatableEmployeeInfo = resultsEmployeeInfo;
// At this point the result in this.reportDatatableEmployeeInfo can be used to update the page
})
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
})
.finally(() => {
this.isBusy = false;
});
},
Displaying the data using EmployeeView.vue class must wait until the data is available. The simplest place to insert updating the page (in the posted code) is within the then handler function inside setReportData.
Displaying the data
As posted setReportData does not notify its caller of when report data is available, either by means of a callback or by returning a promise. All it does is save the result in this.reportDatatableEmployeeInfo and dies.
Without using callbacks, setReportData is left with two choices
Return a promise that is fulfilled with, say,resultsEmployeeInfo or undefined if an error occurs:
setReportData( employeeId) {
this.isBusy = true;
// return the final promise:
return getEmployeeInfo(
employeeId
)
.then( (resultsEmployeeInfo) =>
(this.reportDatatableEmployeeInfo = resultsEmployeeInfo)
)
.catch(() => {
this.alerts.error('An error has occurred while fetching the data');
// return undefined
})
.finally(() => {
this.isBusy = false;
});
},
which could be used in a calling sequence using promises similar to
if(!this.busy) {
this.setReportData(someId).then( data => {
if( data) {
// update page
}
}
If you wanted to make the call in an async context you could use await:
if(!this.busy) {
const data = await this.setReportData(someId);
if( data) {
// update page
}
}
Update the page from within setReportData after the data becomes available ( as shown as a comment in the first part of this answer). The method should probably be renamed from setReportData to getReportData or similar to reflect its purpose.
I'm having trouble finding any resources for this. All I need is help in the right direction to get something tangible to use in some HTML. So basically when I call console.log(keys) I don't get the json objects in my console like I'm used to. Project specifically requests it be done this way via fetch.
function getFromSWAPI() {
fetch("https://swapi.dev/api/people/1")
.then(function (response) {
return response.json()
})
.then(function(data){
updateInfo(data)
})
.catch(function(err) {
console.warn(err)
})
}
const updateInfo = responseJSON => {
console.log(responseJSON.data)
let keys = Object.keys(responseJSON.data)
console.log(keys)
}
So basically there is a minor bug in your code,
In this line
{...}
.then(function (response) {
return response.json()
})
{...}
you have already returned the json and thus you are accessing it in the next line
{...}
.then(function (data) {
updateInfo(data)
})
{...}
As the returned data is itself the real data, you don't have to reaccess it with the data property here
{...}
const updateInfo = responseJSON => {
console.log(responseJSON.data)
let keys = Object.keys(responseJSON.data)
console.log(keys)
}
The finished code will look like this -
function getFromSWAPI() {
fetch("https://swapi.dev/api/people/1")
.then(function (response) {
return response.json()
})
.then(function(data){
updateInfo(data)
})
.catch(function(err) {
console.warn(err)
})
}
const updateInfo = responseJSON => {
console.log(responseJSON)
let keys = Object.keys(responseJSON)
console.log(keys)
}
Alternatively, I would suggest you use async/await if you are using fetch inside a function.
There are several things wrong with this code; only one of them is the direct cause of the trouble you're having with logging, but they will all need to be fixed before this will work.
First: why doesn't console.log work / why are you getting TS error "Cannot convert undefined or null to object at Function.keys"?
Because responseJSON does not have a property named "data", because the API response does not include a property named "data". I suspect that name appears here because you're confused by what HTTP responses are and how Promises work. So let's start at the beginning.
fetch returns an HTTP Response object that contains deep within it some text. You access that text by doing:
let text = await response.text()
Or, if you're using chained .then handlers (as in your sample), you do this:
.then((response) => {
return response.text()
})
If you know that the text is valid JSON (and you do), then you can extract the text and parse it into a value with a single operation: response.json() (which is what you are doing).
This returns a Promise that contains the value encoded by the JSON. If the value is an object, it'll have the properties described by that JSON.
What properties will your object have? Open that URL in your browser, and you'll see them: "birth_year", "created", "edited", "eye_color", "films", "gender", "hair_color", "height", "homeworld", "mass", "name", "skin_color", "species", "starships", "url", "vehicles".
"data" is not on that list.
This is what you are doing with the info from the response:
.then(function (response) {
return response.json()
})
.then(function(data){
updateInfo(data)
})
Thus, you are passing the parsed value directly to the updateInfo function.
The updateInfo function then tries to log the .data property on the received value (which does not exist). That is, .data is undefined.
It is not an error to log an undefined object. But Object.keys will throw if provided with undefined:
Object.keys(undefined)
//> Uncaught TypeError: can't convert undefined to object
The fix is to remove .data from both the log statement and the Object.keys call.
Second: that should have been your first debugging step. Even without understanding all the above: if operations are failing on datapoints buried within a value, the most obvious first debugging step is to "take a step back" and try to examine the variable you're working with.
To illustrate: if console.log(myThing.items[0].parent.nodeName) fails, you should immediately try console.log(myThing) -- this allows you to inspect the myThing variable so you can manually verify whether the path you're accessing is legitimate. One of the most common mistakes made by devs of all experience levels is that they put in a bad data path, simply because to err is human. (Typescript will help you notice this while you're writing code, but you must learn how to trace problems without the help of a tool, or you will always need that tool.)
Third: As I mentioned in the first draft of this post, you're missing some return statements.
Most importantly, your getFromSWAPI function does not return anything. You have to return the fetch if you want callers to receive the data.
function getFromSWAPI() {
return fetch("https://swapi.dev/api/people/1")
// ...rest of chained thens
Also, you need to return something inside the .then handler where you're calling updateInfo. What you return depends on what the purpose of updateInfo is:
if updateInfo is supposed to modify the raw API data for the benefit of downstream code, then you should return the result of calling it:
.then(function(data){
return updateInfo(data)
})
if updateInfo is supposed to cause some kind of side-effect (like updating a local cache with the raw data, or firing an event, etc), then you may want to "bypass" the function: call it, but forward the original value to downstream code:
.then(function(data){
updateInfo(data) // could do _anything_ with data
return data // passes the original data onward
})
Unsolicited code review
You're defining one function using the function keyword, and defining the other as a const arrow function:
function getFromSWAPI() { /* stuff */ }
const updateInfo = responseJSON => { /* stuff */ }
Both patterns work fine, but you should try to be consistent. My advice: if you're still learning JS, prefer the function keyword, because it's more explicit.
Prefer to use async/await instead of chained handlers
You can define functions as async and then await only the specific operations that are asynchronous. In your case, updateInfo does not appear to do any async work, so it kind of sucks that it has to live inside this three-part chained promise. I'd go with this:
async function getFromSWAPI() {
let response = await fetch("https://swapi.dev/api/people/1")
let data = await response.json()
updateInfo(data)
return data
}
I have this code, wherein i'd like to make it in single array.
The output that data produce, is like this:
connections.elements.map((val: any) => {
const url = 'link'
return new Promise((resolve) => {
axios.post(url, val.firstName).then((res: { data: any }) => {
resolve(searchRequestBuilder(res.data.AllResults));
});
});
});
const searchRequestBuilder = async (data: any) => {
console.log(await data);
// for await (let resolvedPromise of data) {
// console.log(resolvedPromise);
// }
};
What I'd like to do is like this:
I already tried to make a variable and use .push, but it still doesn't combine in a single array. What was the thing I am missing?
So since your .map function will return an array of promises, you can store that in a variable, and you can use Promise.all or Promise.allSettled to wait for all the apis to resolve and you can get all the results in a single array in the order that it is requested.
Not part of the question, another thing is if you are not careful, and use await on each api request, it can make a waterfall pattern of requests, which means it will wait for API 1 to finish, then API 2 to finish.
But the way its written here, it will make all the requests parallely, so this will request all the APIs together, and once the last one gets resolved the Promise.all or allSettled will trigger its callback.
const searchRequestBuilder = async (data: any) => {
console.log(await data);
}
async function makeAPIrequests(){
let arrrayOfPromises = connections.elements.map((val: any) => {
const url = 'link'
return new Promise((resolve) => {
axios.post(url, val.firstName).then((res: { data: any }) => {
resolve(searchRequestBuilder(res.data.AllResults));
});
});
});
Promise.allSettled(arrrayOfPromises).
then((results) => console.log(results))
}
Edit:
Also in addition to this, I do not think that you need to return a new Promise inside the map funciton, if you just return axios it should work, since axios itself will return a promise.
The main problem there could be you are making a new call per each item of your elements array. This may create performance issues. The best practice may be to do only one async call and then get all items.
Anyway, if for some reason you really need to make all these ajax calls. Think you are working with asynchronous code, then you really don't know when all calls are answered. To fix this, you need to call your searchRequestBuilder after had made all calls.
I suggest you use Promise.all method.
I left you this link to show you how it works. Basically what you need to do is save all axios.post promises in an array and after loop your elements array call Promises.all passing the array with your promises, then you will be able to execute your searchRequestBuilder without problems.
You can try using .concat:
let allData = [];
allData = allData.concat(await data);
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?
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);
});
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)
});
I'm using sequelize with typescript. I know that the code is asynchronous, and
Here, I am using promise, and the code works..
I would want to know when I must to use await keyword ?
const promises = []
let tabIdDoc = requestedListIdDoc.toString().split(",")
for (let thisIdDoc of tabIdDoc) {
promises.push(sequelize.models['Document'].findById(thisIdDoc))
}
q.all(promises).then((resultReq) => {
const lstDocs = []
for (let MyDoc of resultReq) {
if (MyDoc.matriculeDoc != "") {
lstDocs.push(sequelize.models['FolderDocContent'].findOrCreate({
where: {
}
}))
}
}
q.all(lstDocs).then(() => {
return response.status(201)
})
}
Is await keyword necessary here ?
You don't ever have to use await as other programming using .then() can always get the job done, but there are numerous times when using await makes your code simpler. This is particularly true when you are trying to sequence a number of asynchronous operations and even more so when you need to use previous results in more than one operation that follows.
Example #1: Serializing operations in a for loop
Suppose you want to save a bunch of items to your database, but for various reasons, you need to save them one by one and you need to save them in order (in other words, you need to sequence them):
async function saveItems(shoppingList) {
for (let item of shoppingList) {
// for loop will pause until this promise resolves
await db.save(item);
}
}
saveItems(myList).then(() => {
// all done
}).catch(err => {
// error here
});
Without using await, you'd have to use a significantly more verbose design pattern using .reduce() or perhaps a recursive function that you call on the completion of the prior operation. This is a lot simpler way to sequence iteration of a loop.
Example #2: Sequencing different operations in a function
Suppose, you need to contact three different outside services. You need to get some data from one, then use that data when you make a second API call, then use both of those pieces of data in a third API call:
const rp = require('request-promise');
async function getPrice(productName) {
// look up productID
const productID = await rp(`http://service1.com/api/getID?name=${productName}`);
// use productID to lookup price
const productPrice = await rp(`http://service1.com/api/getPrice?id=${productID}`);
// put both productID and price into the shopping cart
return rp({uri: 'http://service1.com/api/addToCart', body: {name: productName, id: productID}, json: true);
}
getPrice("Nikon_d750").then(total => {
// all done here, total is new cart total
}).catch(err => {
// error here
});
Both of these examples would require more code in order to properly sequence the asynchronous operations and you'd have to nest logic following inside a .then() handler. Using await instructs the JS interpreter to do that nesting for you automatically.
Some other examples here on MDN.
Several rules to keep in mind with await:
You can only use it inside a function prefixed with the async keyword as in my examples above.
All async functions return a promise. If you have an explicit return value in the function as in return x, then that value becomes the resolved value of the promise when all the asynchronous operations are done. Otherwise, it just returns a promise that has an undefined resolved value. So, to use a result from an async function or to know when it's done, you either have to await the result of that function (inside another async function) or you have to use .then() on it.
await only suspends execution within the containing function. It is as if the rest of the code in the function is placed inside an invisible .then() handler and the function will still return its promise immediately when it hits the first await. It does not block the event loop or block other execution outside the async function. This confuses many people when they first encounter await.
If the promise that you await rejects, then it throws an exception within your function. That exception can be caught with try/catch. If it is not caught, then the async function automatically catches it and rejects the promise that the function returns where the thrown value is the reject reason. It is easy when first using await to completely forget about the error cases when promises you are awaiting can reject.
You never necessarily must use await. You however should use await in every place where you'd otherwise have then with a callback, to simplify your code.
In your example:
const promises = requestedListIdDoc.toString().split(",").map(thisIdDoc =>
sequelize.models['Document'].findById(thisIdDoc)
);
const resultReq = await q.all(promises);
const lstDocs = resultReq.filter(myDoc => MyDoc.matriculeDoc != "").map(myDoc =>
sequelize.models['FolderDocContent'].findOrCreate({
where: {}
})
);
await q.all(lstDocs);
return response.status(201)