Javascript - Pass anonymous functions to Promise.all - javascript

I'm trying to make multiple API calls concurrently using the Promise.all method with Axios, based on this example:
getUsers() {
return axios.get('/users');
}
getSessions() {
return axios.get('/sessions');
}
Promise.all([getUsers(), getSessions()])
.then(results => {
// Use the data
})
.catch(error => {
// Catch the error
});
However, as I will only know what concurrent API calls I need to make at this stage based on the result of a previous API call in the promise chain, I'm attempting to pass in an array of anonymous functions to the function in the form of:
var array = [];
array.push(() => {return axios.get('/users')});
array.push(() => {return axios.get('/sessions')});
Promise.all(array).then....
This doesn't work, and I understand that this is because I'm passing in function objects instead of referencing actual Promise objects as the method expects. However, pushing just axios.get(...) methods to the array results in them being called immediately, instead of later when the Promise.all method is executed.
I'm not sure how to do this properly, or whether there's a better approach to achieve what I'm after...

I am not familiar with Axios, but if I understand correctly axios.get returns a Promise, which you need for Promise.all. How about:
Promise.all(array.map(f => f()).then....
That way your functions are called when you actually want, and the map will give you an array of their results, so an array of Promises.
Note that this is essentially the same as the example you have cited with [getUsers(), getSessions()] – with the difference that your functions are anonymous and called implicitly using the map, instead of explicitly by their names. This way you have more flexibility in which functions actually get called.

Basically, in siplest scenario, to use anonymous function as a wrapper, you need to call it before you are put it to Promise.all.
So just try to :
const f = async () => {return service.myFunc();}
Promise.all([f()])
so, your wrapper (f) calling before, and you are providing array with one Promise.
or
Promise.all([(async () => {const result = await service.myFunc(); return result;})()]);
or (equal) :
const myPromise = (async () => {const result = await service.myFunc(); return result;})();
Promise.all([myPromise]);
So Promise will be provided to Promise.all, not just raw function. Also, be carefully and do not forget to use await in case of "big anonym function body".

i've been scratching my head about this for the longest time. in the first example you call the functions here:
Promise.all([getUsers(), getSessions()])
but in the second the anonymous functions are just stated, never called:
array.push( ()=>{return axios.get('/users')} );
so you would need to do this and call them as you pushed them:
array.push( (()=>{return axios.get('/users')}) () );

Related

Instruction not waiting properly NodeJS [duplicate]

This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 1 year ago.
I was wondering what is the problem here, why is the return statement not waiting to finish the previous instruction? orders are empty, even though I'm logging inside the getOrders to make sure there were data (there is data).
await stores.forEach(async (store) => {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
})
return orders;
To wait for all promises, you need an array of promises, which you can get using map. Then you need to use Promise.all, so that it will wait for all the promises in that array to complete.
const orders = await Promise.all(
stores.map(store => getOrders(store, token, tokenType))
);
return orders;
*Edit: rewritten and simplified based on suggestion from #PatrickRoberts
To paraphrase his comment, in the old code when using a separate array with an array.push, the array ordering would be indeterminate. However using the array of results from Promise.all will always match the order of the original array.
It's a common misunderstanding that asynchronous callbacks (declared with async and called with await) passed to forEach or map will be executed synchronously or in order. Actually, they are not.
When you check the polyfill of those methods, you'll find a line like this callback.call(T, kValue, k, O);. So, basically, it just execute the callback. If the callback is an asynchronous method, it doesn't wait its execution to be done. Instead, it continue executing other codes.
Let's set an example using your code, let' say this is the callback:
const callback = async (store) => {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
})
When it was passed to forEach:
stores.forEach(callback);
basically, what forEach does is iterating the array, and call callback on each elements. May looks like this
...
loop {
callback.call(...)
}
...
There is no wait here. However, each time, the code inside your callback is executed synchronously since it's declared with async and called with await, which means after getOrders is resolved and the result is pushed to orders.
So, if you want to execute the callbacks synchronously and in order, you may write a generic for loop.
async function addOrders(){
for(let store of stores) {
let arr = await getOrders(store, token, tokenType);
orders.push(arr);
}
}
addOrders()

Firebase realtime database - add .remove() to promise array executes before Promise.all()

I am adding a set of instructions to be performed by Promise.all() at the end of my code.
What I have noticed is that the .remove() instructions are executed even if Promise.all() is NOT called. (??)
My code:
const myObject = { test:1 }
let myPromises = []
// This .set() will not get executed since I am not calling Promise.all()
console.log(`dbRoot.child('${path}/${uid}/${id}').set(myObject)`)
myPromises.push(dbRoot.child(`${path}/${uid}/${id}`).set(myObject))
// This .remove() gets executed at once, even though Promise.all is never called
console.log(`dbRoot.child('${path}/${uid}/${id}').remove()`)
myPromises.push(dbRoot.child(`${path}/${uid}/${id}`).remove())
// These lines have been excluded to prevent promises from being carried out
/*
return Promise.all(myPromises)
.then(result => Promise.resolve(true))
.catch((err)=>{
console.error('error', err)
return Promise.reject(err)
})
*/
I have checked my code and .set(null) or .remove() is not called from anywhere else but these lines.
How can I collect all .set() and .remove() and carry them all out at once synchronously? (Maybe using Promise.all ?)
Kind regards /K
If you don't want a promise to execute immediately, simply wrap it in a function. You can then execute each function only when you call Promise.all:
const actions = [
() => dbRoot.child(`${path}/${uid}/${id}`).set(myObject),
() => dbRoot.child(`${path}/${uid}/${id}`).remove(),
]
;(async () => {
const results = await Promise.all(actions.map(fn => fn()))
console.log(results)
})()
Note that Promise.all executes promises in parallel. If you instead want them executed in series, use Array#reduce or for await ... of instead.
To quote the documentation for both .set and .remove
The effect of the write will be visible immediately, ... the returned Promise will resolve when complete. If provided, the onComplete callback will be called asynchronously after synchronization has finished.
i.e. As soon as you call the .set or .remove method, firebase will attempt to perform the write action.
Re: Using Promise.all
Do you mean this is an incorrect approach when calling Set/Remove?
No, I'm not saying this is an incorrect approach. What I'm saying is that the write action happens immediately, regardless of whether or not you use Promise.all.
All that Promise.all does, is ensure that every promise in the provided array has been resolved.
How can I collect all .set() and .remove() and carry them all out at once synchronously?
Promise.all
Sources:
Set, Remove

Knex.js non-terminating functions

I don't have any problem in this question, I am just interested in how knex.js menaged to something.
In code, you can write something like this
let search = knex.table('users').select('something')
if(params.minprice) search.where('minprice', params.minprice)
if(something) search.something()
let result = await search
It works, but I don't get how did they menage to hold query execution until await occured? If we did await, it means function was async aka returned a promise. But in javascript, promise executes as soon as function that returned it is called, it does not care is there .then() or .catch(). Should not query execution start al line 1? What is more, when I log search, it is not a promise, but some kind of object, so how can it be awaited?
Can someone provide a simple example how to achieve such a behaviour?
I'm guessing that search contains a property named then, which is a function that initiates the search and also behaves similarly to the functionality of Promise.prototype.then.
E.g.:
// define Searchable
let Searchable = function() {
this.searchParam = 'param';
};
Searchable.prototype = {
setSearchParam: function(p) { this.searchParam = p; },
initiateSearch: async function() {
// lots of fancy searching
console.log(`Searching with param "${this.searchParam}"`);
return `search based on "${this.searchParam}"`;
},
then: async function(callback) {
// initiate the search:
let searchResults = await this.initiateSearch();
// behave kind of like `Promise.prototype.then`!
return callback(searchResults);
}
};
// now work with it:
(async () => {
let searchable = new Searchable();
searchable.setSearchParam('mySearchParam');
console.log('No search performed yet!');
// Here's the fancy usage you're concerned with (it invokes `searchable.then`):
let searchResult = await searchable;
console.log('RESULT:', searchResult);
})();
Calling await on some value will attempt to call value.then as if it were a function accepting a callback argument.
Knex query builder is mutable and thenable object.
So every time you call for example search.where(...) for that query builder, its internal state changes and stores that new where clause.
Query builder being thenable means that the object has .then() method and when you call await search it is actually pretty much equivalent with await Promise.resolve(search) which first executes thenable and converts it to promise which is then resolved or an exception might occur.
Thenable objects are actually pretty important part of promise spec providing interoperability API between promises and non-promise objects.

Resolve Promise After Executing Single Line Of Code

I have a sequence of events I'm trying to align, but I don't understand how to resolve a promise after a single line of asynchronous code. In this case, I'm making an API call which resolves to an array of objects. I'm then using Object.assign() to convert the array of objects into an object of objects. Once completed, I then want to call a function which does something with that new object of objects. The conversion process takes some time, and I'm not sure how to postpone my function call until after the object has been converted. My non-functioning code is given below.
this.queryCodes().then(() => {
new Promise((resolve) => {
resolve(() => {
// This code isn't executing.
this.floorCodesLookup = Object.assign({}, ...this.floorCodes);
});
}).then((data) => {
this.resolveCodesToDescriptions();
});
});
resolve should be given the result of your asynchronous operation, not the code to execute. The code you want to execute shouldn't go in the resolve argument, but instead in the callback you give to the promise constructor. So for your snippet:
this.queryCodes().then(() => {
return new Promise((resolve) => {
// Put the code you want to execute here...
// Call resolve when your work is done, and pass it the result if there is one.
resolve(/* maybe result here */);
}).then((data) => {
this.resolveCodesToDescriptions();
});
});
It's also worth noting that you don't need to use a promise constructor since you're already chaining off a promise to begin with. You could just:
this.queryCodes().then(() => {
// Do additional work here...
this.floorCodesLookup = Object.assign({}, ...this.floorCodes);
return this.resolveCodesToDescriptions();
});
resolve() doesn't take a callback as an argument. It takes the resolved value as an argument. Not sure where you got the callback notion from. That's why the callback is never called because that's not a supported feature of resolve().
Manually creating a promise inside a .then() handler is likely not needed and is probably an anti-pattern. You don't show enough of the real code here to understand what is going on, but this is likely way more complicated than it needs to be. From the code you show, I think you can just do this:
this.queryCodes().then(() => {
this.floorCodesLookup = Object.assign({}, ...this.floorCodes);
this.resolveCodesToDescriptions();
});

forEach loop with async code?

I am making a function call 3 times with different arguments:
this.getContributorProperties('followers_url', 'contributorFollowers');
this.getContributorProperties('gists_url', 'contributorGists');
this.getContributorProperties('repos_url', 'contributorRepositories');
This function looks like that:
async getContributorProperties(propertyUrl, propertyName) {
const contributors = await this.addLinkToContributor();
for (let i = 0; i < 10; i += 1) {
axios.get(`${contributors[i][propertyUrl]}?per_page=100&${API_KEY}`).then((res) => {
contributors[i][propertyName] = res.data.length;
});
}
return contributors;
}
It loops through an array of contributors (object type) and makes an API call for each one of them. I need to make 3 API calls for each one of them hence the three calls at the beginning.
In order to DRY up my code I wanted to make a forEach loop like so:
[
['followers_url', 'contributorFollowers'],
['gists_url', 'contributorGists'],
['repos_url', 'contributorRepositories'],
].forEach(this.getContributorProperties);
forEach loop is in componentDidMount()
When I make 3 calls it works ok. But when I do forEach I get an error:
Uncaught (in promise) TypeError: Cannot read property 'addLinkToContributor' of undefined
How do I make it work?
BONUS: How do I then assign those key-value pairs to each object?
See How to access the correct this inside a callback? and/or How does the "this" keyword work? for why you had that specific error message.
But fundamentally, you wouldn't want to just pass that function into forEach anyway, since forEach doesn't pass that function what it wants.
Instead, just use an arrow function:
[
['followers_url', 'contributorFollowers'],
['gists_url', 'contributorGists'],
['repos_url', 'contributorRepositories'],
].forEach(pair => this.getContributorProperties(pair[0], pair[1]).catch(err => {/*...handle error...*/});
Note the error handling; we don't want unhandled rejections, and forEach doesn't do anything to propagate them to the caller.
It seems odd, though, not to use the return value for anything. Perhaps map and Promise.all:
const results = await Promise.all([
['followers_url', 'contributorFollowers'],
['gists_url', 'contributorGists'],
['repos_url', 'contributorRepositories'],
].map(pair => this.getContributorProperties(pair[0], pair[1])));
...being sure to handle errors if the caller won't.
Please note there are two bugs in your getContributorProperties function:
It doesn't wait for the axios.get to complete before returning (async functions don't auto-await promises, you have to be explicit)
It doesn't handle rejections of the promise returned by axios.get
I'm also curious if repeating the call to this.addLinkToContributor three times is correct, and whether it may be wasteful the second two times.
In a comment you've asked:
Results are 3 arrays of the same objects (contributors) with just one property changed each. So one array has contributors with gists, another with followers, etc. Do I now somehow concatenate them or is it better to do that in getContributorProperties function?
That would be my instinct. Something like:
async getContributorProperties(properties) {
const contributors = await this.addLinkToContributor();
return Promise.all(contributors.map(contributor =>
Promise.all(properties.map(property =>
axios.get(`${contributor[property.url]}?per_page=100&${API_KEY}`).then(res => {
contributor[property.name] = res.data.length;
})
));
));
}
called like this:
const results = await this.getContributorProperties([
{url: 'followers_url', name: 'contributorFollowers'},
{url: 'gists_url', name: 'contributorGists'},
{url: 'repos_url', name: 'contributorRepositories'}
]);
(We really need that await.all concept so that the above isn't mixing its metaphors...)

Categories