How to pass information around when working with JavaScript Promise - javascript

At one point, we are calling our backend several times with fetch. Each call returns a promise, which we collect and pass to our responseHandler. This handler is supposed to dispatch redux actions when each response comes in. The action will be shaped differently based on two factors:
the response code of the server
the content of the request
so we have this
function save(objects) {
let promises = objects.map(object => {
let promise = fetch(url, {..., body: object});
});
responseHandler(promises);
}
function responseHandler(promises) {
for (let promise of promises) {
promise
.then(r => {
return r.json();
})
.then(body => dispatchSomeAction(body));
}
}
So far so good. But now we need info from inside of the save and from inside of the anonymous function that receives the response object. So I came up with the following approach. But it feels wrong to piggyback on the promise object. I can't say why.
function save(objects) {
let promises = objects.map(object => {
let promise = fetch(url, {..., body: object});
promise._objectType = object._type;
});
responseHandler(promises);
}
function responseHandler(promises) {
for (let promise of promises) {
promise
.then(r => {
promise._status = r.status;
return r.json();
})
.then(body => dispatchSomeAction({
status: promise._status,
type: promise._type, ...body
}));
}
}
As can be seen, every time I want a value of a scope to be available later, I place it on the promise object because that promise object is in all the other scopes. Is there a cleaner way?
Note: The backend returns an object that doesn't hold the _type property (actually I clear my objects of those flags before sending them to the backend. It's a frontend flag that I still require when the answer comes)

You must carry the object. Using ES6 structuring and destructuring is not very clumpsy:
function save(objects){
let promises = objects.map(object => {
return fetch(url, {..., body: object}).then(r => ({r,type:object._type}));
}
responseHandler(promises)
}
function responseHandler(promises){
for (let promise of promises){
promise
.then(({r,type}) => {
return r.json().then(body => dispatchSomeAction({status: r.status, type, ...body));
});
}
}
Note that you don't really need to do anything at all with the status, as r it was still in scope where it's used.

I would try something like the following
function save(objects){
let promises = objects.map(object) =>{
return fetch(url, {..., body: object}).then(r=>{
return {data: r, originalObject: object}
})
}
responseHandler(promises)
}
function responseHandler(promises){
for (let promise of promises){
promise
.then(r => {
return {data: r.data.json(), originalObject: r.originalObject)
.then(r=> dispatchSomeAction({status: r.data.status, type: r.originalObject.type, ...r.data.body));
}
}
you can tweak a little bit, say, without passing the whole object, just the type field.

A common pattern is to use a single global object (commonly called state or appData or similar) to store values and allow you to access them from within different closures.
Here's a contrived example of using this pattern to load a bunch of items, and then to reload the data for each item after clicking a "refresh" button:
const state = {}
getItemsPromise().then(response => {
state.items = response.items
})
$(refreshItemsButton).on('click', ()=>{
state.items.forEach(item => {
refreshItemPromise(item).then(response => {
state.items[item.id] == response.item
})
})
})

Related

JavaScript Promise inside async/await function resolve final array of responses

I'm quite a newbie in JavaScript and in Promises.
I'm trying to build an array of objects that I get from an API.
To do so, I've build two functions in a file MyFile.js.
The first one returns a promise when an axios promise is resolved. It's
function get_items (url) {
return new Promise((resolve, reject) => {
let options = {
baseURL: url,
method: 'get'
}
axios(options)
.then(response => {
resolve(response.data)
})
.catch(error => {
reject(error.stack)
})
})
}
The second one looks like this:
let output = []
let next_url = 'https://some_url.com/api/data'
async function get_data () {
try {
let promise = new Promise((resolve, reject) => {
if (next_url) {
get_items(next_url)
.then(response => {
output.push(...response.results)
if (response.next) {
next_url = response.next
console.log('NEXT_URL HERE', next_url)
get_data()
} else {
console.log('else')
next_url = false
get_data()
}
})
.catch(error => {
reject(error.stack)
})
} else {
console.log('before resolve')
resolve(output)
}
})
return await promise
} catch(e) {
console.log(e)
}
}
It's where I'm grinding my teeth.
What I think I understand of this function, is that:
it's returning the value of a promise (that's what I understand return await promise is doing)
it's a recursive function. So, if there is a next_url, the function continues on. But if there is not, it gets called one last time to go into the else part where it resolves the array output which contains the results (values not state) of all the promises. At least, when I execute it, and check for my sanity checks with the console.log I wrote, it works.
So, output is filled with data and that's great.
But, when I call this function from another file MyOtherFile.js, like this:
final_output = []
MyFile.get_data()
.then(result => {
console.log('getting data')
final_output.push(...result)
})
it never gets into the then part. And when I console.log MyFile.get_data(), it's a pending promise.
So, what I would like to do, is be able to make get_data() wait for all the promises result (without using Promise.all(), to have calls in serie, not in parallel, that would be great for performances, I guess?) and then be able to retrieve that response in the then part when calling this function from anywhere else.
Keep in mind that I'm really a newbie in promises and JavaScript in general (I'm more of a Python guy).
Let me know if my question isn't clear enough.
I've been scratching my head for two days now and it feels like I'm running in circle.
Thanks for being an awesome community!
This is a bit untested
const api_url = 'https://some_url.com/api/data';
get_data(api_url).then((results) => {
console.log(results);
}).catch((error) => {
// console.error(error);
});
function get_items (url) {
const options = {
baseURL: url,
method: 'get'
};
return axios(options).then((response) => response.data);
}
async function get_data(next_url) {
const output = [];
while (next_url) {
const { results, next } = await get_items(next_url);
output.push(...results);
next_url = next;
}
return output;
}
Basically it makes things a bit neater. I suggest to look at more examples with Promises and the advantage and when to ease await/async. One thing to keep in mind, if you return a Promise, it will follow the entire then chain, and it will always return a Promise with a value of the last then.. if that makes sense :)
There are a few problems. One is that you never resolve the initial Promise unless the else block is entered. Another is that you should return the recursive get_data call every time, so that it can be properly chained with the initial Promise. You may also consider avoiding the explicit promise construction antipattern - get_items already returns a Promise, so there's no need to construct another one (same for the inside of get_items, axios calls return Promises too).
You might consider a plain while loop, reassigning the next_url string until it's falsey:
function get_items (baseURL) {
const options = {
baseURL: url,
method: 'get'
}
// return the axios call, handle errors in the consumer instead:
return axios(options)
.then(res => res.data)
}
async function get_data() {
const output = []
let next_url = 'https://some_url.com/api/data'
try {
while (next_url) {
const response = await get_items(next_url);
output.push(...response.results)
next_url = response.next;
}
} catch (e) {
// handle errors *here*, perhaps
console.log(e)
}
return output;
}
Note that .catch will result in a Promise being converted from a rejected Promise to a resolved one - you don't want to .catch everywhere, because that will make it difficult for the caller to detect errors.
Another way of doing it is to not use async at all and just recursively return a promise:
const getItems = (url) =>
axios({
baseURL: url,
method: 'get',
}).then((response) => response.data);
const getData = (initialUrl) => {
const recur = (result, nextUrl) =>
!nextUrl
? Promise.resolve(result)
: getItems(nextUrl).then((data) =>
recur(result.concat([data.results]), data.next),
);
return recur([],initialUrl)
.catch(e=>Promise.reject(e.stack));//reject with error stack
};
As CertainPerformance noted; you don't need to catch at every level, if you want getData to reject with error.stack you only need to catch it once.
However; if you had 100 next urls and 99 of them were fine but only the last one failed would you like to reject in a way that keeps the results so far so you can try again?
If you do then the code could look something like this:
const getData = (initialUrl) => {
const recur = (result, nextUrl) =>
!nextUrl
? Promise.resolve(result)
: getItems(nextUrl)
.catch(e=>Promise.reject([e,result]))//reject with error and result so far
.then((data) =>
recur(result.concat([data.results]), data.next),
);
return recur([],initialUrl);//do not catch here, just let it reject with error and result
};

Using promises in React to wait for functions to finish within JavaScript map function

I've been trying to figure out what the proper way would be to write a promise for this function. I have an asynchronous function that makes an HTTP request to the server to retrieve a response, "documents_fileUploader." I am mapping through the "url" of each item within the response, and each url will go in to a function that makes another HTTP request and then sets the state. I want to fire the "upload()" function only after everything within the "documents_fileUploader()" function is complete. I tried doing this without a promise and it went straight to my "upload()" function because request was still pending. Any suggestions on how to go about this?
documents_fileUploader(formData).then(resp => {
resp.data.items.map(url => {
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
this.getFileObject(key);
})
}).then(() => {
this.upload();
})
getFileObject = file => {
file_view(file).then(resp => {
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}
To your main question, you can wait for every promise that your .map call returns by using the Promise.all method.
Second, in order for that to work, your getFileObject function must return the promise it creates.
So incorporating those two changes, your snippet might look like:
documents_fileUploader(formData).then(resp => {
return Promise.all(resp.data.items.map(url => { // Wrap in Promise.all and return it
const key = url.split("/")[4];
this.setState({
urls: [...this.state.urls, url],
keys: [...this.state.keys, key]
});
return this.getFileObject(key); // Make sure to return this promise as well.
}));
}).then(() => {
// Now this won't happen until every `getFileObject` promise has resolved...
this.upload();
})
getFileObject = file => {
return file_view(file).then(resp => { // And return the promise here.
this.setState({
mimeTypes: [...this.state.mimeTypes, resp.data.item.headers.contentType]
})
}).catch(err => {
console.log(err);
})
}

How do I return the value of the promise, instead of returning the promise itself? (React/Redux) [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 4 years ago.
I am having an issue within one of my reducers where I have all of the information readily available to return to the front-end except for one array called "items" which I need to do an additional fetch to get more data back. The issue I am having with this fetch is that it ultimate leads to a return of a promise instead of returning the value. My question is how can I get the value instead of the promise? (e.g. via blocking everything else until the promise resolves and then sending that back).
I have commented throughout the below code to hopefully explain everything more clearly:
export const getItemsForHistory = createSelector(
[getAllHistory, getAllItems, getHistoryPage], (history, items, page) => {
let itemIds = `[a bunch of ids]`;
//this is the call I am making to fetch the additional data for "items"
//this return statement is sending back a promise and not a value
return getUpdatedItems(itemIds, items).then(result => {
//result is the actual value I want
return result;
}, error => {
//if the fetch fails, I would like to send a default value for "items"
//note, if I place this return outside of this entire promise and remove the promise, it works as expected with the default data
return history.map(h => items.get(h.get('item'), null))
}).catch(error => {
return history.map(h => items.get(h.get('item'), null))
})
}
)
//I would like for this method to run asychronously
const getUpdatedItems = async (itemIds, items) => {
let result = await fetchHistoryItemsDetail(itemIds);
return result;
}
const fetchHistoryItemsDetail = (itemIds) => {
let headers = {
"Content-Type": "application/json",
},
fetchUrl = `XXXXXXXXX`;
return fetch(fetchUrl, {
method: "POST",
headers: headers,
body: itemIds,
withCredentials: true,
crossDomain: true,
})
.then(response => {
return response.json();
})
.catch(error => {
throw error;
})
}
export const getSortedHistory = createSelector(
[getAllHistory, getItemsForHistory, getHistorySort, getHistoryPage], (history, items, sort, page) => {
//this is where items is ultimately retured to the front end but it is now a promise and not an array of data
//(from getItemsForHistory)
return sortedEntities(history, items, sort)
}
)
I hope that my question is clear, but please let me know if further elaboration is needed.
You cannot return data from within a promise's cats/error handler
If for both you want to send data, I would suggest wrapping in another promise that will always resolve
return new Promise((res) => {
//this is the call I am making to fetch the additional data for "items"
//this return statement is sending back a promise and not a value
getUpdatedItems(itemIds, items).then(result => {
//result is the actual value I want
res(result);
}, error => {
//if the fetch fails, I would like to send a default value for "items"
//note, if I place this return outside of this entire promise and remove the promise, it works as expected with the default data
res(history.map(h => items.get(h.get('item'), null)));
}).catch(error => {
res(history.map(h => items.get(h.get('item'), null)));
})
});

Why this promise chain is stuck at one of then calls and catch returns empty error object?

I'm making a post request and it requires several things to happen. It's native JavaScript promise not any library. Initially used nested promise and it was working but code was not that good. So, I decided to go with Promise chain and I'm stuck. The post route always returns {success:false, err:{}}, which it should when something goes wrong. But the err object is empty object. Why is that? After some tests I found the problem is in the second then where I'm returning AvaiablexForX.findOne({isX:false});. Don't worry about variables names, for the sake idea I have changed the actual names.
router.post("/delevery_request",
passport.authenticate("jwt", {session:false}),
(req, res) => {
const requestInputFields = {};
const foundxProfile = {};
const xProfileId = "";
const newxRequestId = "";
requestInputFields.isAccepted = false;
XProfile.findOne({user:req.user.id})
.then(xProfile => {
foundxProfile= xProfile;
requestInputFields.xId = xProfile._id;
return AvaiablexForX.findOne({isX:false});
})
.then( avaiablexForX =>
{
// this does not reach here
console.log("available x for X", avaiablexForX);
requestInputFields.xToBeDonateId = avaiablexForX._id;
xProfileId = avaiablexForX.xProfileId;
return requestInputFields;
})
.then( result => new RequestxY(result).save()).
then( requestForxY => {
foundxProfile.requestedxDeleivery.unshift(requestForxY._id);
return foundxProfile.save();
}).
then( xProfile => res.json({success:true}))
.catch(err => {
// output in body of request: {success:false, err:{}}
res.status(404).json({success:false, err:err})
}
);
});
Probably the problem is you trying to set a new value for const:
foundxProfile= xProfile;
This cause error and broke the chain. Try to replace all const by let.
Short answer: as already pointed out members declared with const can't be reassigned.
Long answer: you would benefit from a better strategy for accessing previous promise results in a .then() chain
With ref to the linked topic, you are using the "inelegant and rather errorprone" mutable contextual state.
You might consider one of the other approaches :
Nesting (and) closures
Break the chain
Explicit pass-through
For example, Nesting (and) closures would give you something like this :
router.post('/delevery_request', passport.authenticate('jwt', { 'session': false }), (req, res) => {
XProfile.findOne({ 'user': req.user.id })
.then(xProfile => {
return AvaiablexForX.findOne({ 'isX': false })
.then(avaiablexForX => {
return new RequestxY({
'isAccepted': false,
'xId': xProfile._id,
'xToBeDonateId': avaiablexForX._id
}).save();
})
.then(requestForxY => {
xProfile.requestedxDeleivery.unshift(requestForxY._id);
return xProfile.save();
});
})
.then(() => res.json({ 'success': true }))
.catch(err => {
res.status(404).json({
'success': false,
'err': err
});
});
});
Due to closure, xProfile is available to the first and second nested .then().
What was requestInputFields is composed on the fly where it is used.
You lose the nice flat line up of then()s but gain by not needing a bunch of messy outer members.

Promise as a class method call triggers object.then upon resolving

I have an class that contains db methods and it's wrapped in a proxy which handles access to properties. Since the issue is related to promises here is an simplified example code that is reproducing the same issue:
const handler = {
ownKeys(target) {
return Object.keys(target._attributes)
},
get(target, property) {
console.log(`<-${property}`) // <-- this logs what properties are being accessed
if (typeof target[property] !== 'undefined') {
return Reflect.get(target, property)
}
return Reflect.get(target._attributes, property)
},
set(target, property, value) {
target._attributes[property] = value
return true
}
}
class User {
static table = 'users'
static fetch(query = {}, opts = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(new this(query))
}, 500)
})
}
constructor(attributes = {}) {
this._attributes = attributes
return new Proxy(this, handler)
}
}
async function trigger() {
const user = await User.fetch({test:'test'})
console.log(JSON.stringify(user._attributes))
}
trigger()
Everything works well, during the testing I've added a printout to the proxy to determine performance hit of using such model design, and I noticed that my model get's called from within promise chain.
Example output follows:
<-then
<-_attributes
{"test":"test"}
I guess that returning new this(query) causes the promises to think that maybe it's a promise returned and consequently .then() is executed.
Only workaround that I've found is to wrap resolve response inside new array or another object like this:
static fetch(query = {}, opts = {}) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([new this(query)])
}, 500)
})
}
// Output
// 2
// <-_attributes
// {"test":"test"}
What I'm wondering is, is this correct way and are there other solutions to this side effect?
All objects passed to as a result of a promise is always checked to see if it has a then property. If it does, that function is used to queue up entries to get a final value. This is why logic like
Promise.resolve()
.then(() => {
return Promise.resolve(45);
})
.then(result => {
console.log(result);
});
logs 45 instead of a promise object. Since the promise objects has a .then property, it is used to unwrap the promises value. The same behavior happens in your resolve(new this(query)) case, because it needs to know if there is a value to unwrap.
As you've said, commented in your post, you could certainly wrap the instance with a non-proxy, e.g.
resolve({ value: new this(query) })
which would check for .then on that object instead of on your proxy, but then you have to do .value to actually get the proxy, which can be a pain.
At the end of the day, that is a choice you'll have to make.

Categories