I am creating a module that is executing tasks based on a config it receives. These tasks are asynchronous and are returning a promise. Currently there are only two tasks to handle, but if there are more coming up, I will run into a problem of identifying which result of Promise.all() belongs to which task.
Here is a snap of my current code:
let asyncTasks = [];
let result = {};
if (config.task0) {
asyncTasks.push(task0(param));
}
if (config.task1) {
asyncTasks.push(task1(param));
}
Promise.all(asyncTasks)
.then(results => {
// TODO: There has to be a prettier way to do this..
if (config.task0) {
result.task0 = results[0];
result.task1 = config.task1 ? results[1] : {};
} else if (config.task1) {
result.task0 = {};
result.task1 = results[0];
} else {
result.task0 = {};
result.task1 = {};
}
this.sendResult(result)
});
The config looks like this:
const config = {
task0: true,
task1: true
};
As mentioned in the code, there has to be a prettier and more scaleable way to identify which result is coming from which task, but I can't find anything regarding Promise.all() that could help with this.
How do I identify which value belongs to which promise if Promise.all() resolves?
Promise.all resolves with an array of values, where each value's index in the array is the same as the index of the Promise in the original array passed to Promise.all that generated that value.
If you need anything more fancy you'll need to keep track of it yourself or use another library that offers such functionality (like Bluebird).
There's really no need to use anything other than Promise.all. You're experiencing difficulty because the other structure of your program (config, and arbitrary link of config key to function) is pretty messy. You might want to consider restructuring the code altogether
const config = {
task0: true,
task1: true,
task2: false
}
// tasks share same keys as config variables
const tasks = {
task0: function(...) { ... },
task1: function(...) { ... },
task2: function(...) { ... }
}
// tasks to run per config specification
let asyncTasks = Object.keys(config).map(prop =>
config[prop] ? tasks[prop] : Promise.resolve(null))
// normal Promise.all call
// map/reduce results to a single object
Promise.all(asyncTasks)
.then(results => {
return Object.keys(config).reduce((acc, task, i) => {
if (config[task])
return Object.assign(acc, { [prop]: results[i] })
else
return Object.assign(acc, { [prop]: {} })
}, {})
})
// => Promise({
// task0: <task0 result>,
// task1: <task1 result>,
// task2: {}
// })
Note: we can depend on the order of results because we used Object.keys(config) to create the input array of promises and Object.keys(config) again to create the output object.
Related
I'm testing a function to see if, when called, it will return the proper created list.
To start, I create the elements, using the createDesign.execute() functions. It's tested on another file and working.
Then, I call the function I want to test: listAllDesigns.execute() and store it's value in a variable.
If I console.log(list), it returns the full list properly.
In pseudocode, what I'd like to do is: Expect list array to have an element with the design object and, within it, a design_id that equals "payload3".
How should I write this test?
Is there a better way to do this? (other than checking if list !== empty, please)
it('should return a list of all designs', async () => {
// Create fake payloads
const payload1 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload1' } },
};
const payload2 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload2' } },
};
const payload3 = {
...defaultPayload,
...{ design: { ...defaultPayload.design, design_id: 'payload3' } },
};
await createDesign.execute(payload1);
await createDesign.execute(payload2);
await createDesign.execute(payload3);
const list = await listAllDesigns.execute();
// expect(list). ????
});
The easiest method would be a combination of expect.arrayContaining and expect.objectContaining like so:
expect(list).toEqual(
expect.arrayContaining([
expect.objectContaining({
design: expect.objectContaining({
design_id: "payload3"
})
})
])
);
There is a question about for await ... of loop.
I have the following code:
for await (let V of reagentItems.map(objectElement => getPricing(objectElement))) {
console.log(objectElement) //I'd like to have access to the current iteration.
//or
console.log(reagentItems[i]) //current fulfilled element from reagent.map
}
So the problem is that this array (from .map) of functions (V as a single response of getPricing) doesn't return the objectElement itself but I need to have access to this objectElement inside for await ... of loop.
Does it possible somehow or not? And if it doesn't, using of Promise.all handles this problem somehow? Or I should modify the original getPricing and return current objectElement with other results?
From what I can understand from your question and your comments, you want to be able to access both object_element and the result of getPricing(object_element) within the for loop.
Your problem right now is that as a result of map, you only have the result of getPricing, but not the original object_element. As a solution, simply return both from map in an object:
// just a random getPricing function
function getPricing(objectElement) {
return {
item_id: objectElement.id,
price: Math.random()
};
}
const reagent_items = [
{
id: 'a'
},
{
id: 'b'
}
];
for (let V of reagent_items.map(object_element => ({object_element, pricing: getPricing(object_element)}))) {
console.log(`object_element: ${JSON.stringify(V.object_element)}, pricing: ${JSON.stringify(V.pricing)}`);
}
You should not be using for await here at all - that is meant for asynchronous generators. Instead, do
for (const object_element of reagent_items) {
const V = await getPricing(object_element);
console.log(object_element, V)
…
}
With Promise.all, the code would look like this:
Promise.all(reagent_items.map(object_element =>
getPricing(object_element).then(V => {
console.log(object_element, V);
…
})
)).then(…)
// or:
await Promise.all(reagent_items.map(async object_element => {
const V = await getPricing(object_element)
console.log(object_element, V);
…
}));
If you look at the picture both arrays consist of same kind of object. first I create it with empty data as placeholder, but second one I create it with data coming from server.
writeValue(v: any) {
console.log('aaa');
console.log(v);
console.log('aaa');
this.form = new FormArray([]);
for (const value of v) {
console.log('bbb');
console.log(value);
console.log('bbb');
this.form.push(new FormControl(value));
}
this.form.valueChanges.subscribe(res => {
if (this.onChange) {
this.onChange(this.form.value);
}
});
}
for first case it goes through all of the writeValue code, for second one it doesn't go through the for(const values of v) code. why is this happening? when I print them out they seem to be the same other than one difference [{...}] vs [] in browser tools.
If you want to see how I create them. the first one is routes and the second one is routeslocal. I put them in angular formcontrol, and thats how it gets to writeValue via controlvalueaccessor. If you want to know how it works you could check my previous question here. there is more code, but it doesn't include the service.
ngOnInit() {
const routes: any[] = [];
routes.push({ ...dataI });
this.requestForm = this.fb.group({
statusId: null,
requestVehicles: this.fb.array([
this.fb.group({
garageId: 0,
routes: new FormControl(routes),
endDateTime: 0,
})
])
});
if (this.data.isEdit) {
this.Title = 'Edit';
this.data.fService.getRequest(this.data.requestId).subscribe(thisRequest => {
this.requestForm = this.fb.group({
statusId: thisRequest.status,
requestVehicles: this.fb.array([
])
});
thisRequest.requestVehicles.forEach((element, index) => {
const routeslocal: any[] = [];
element.routes.forEach((elementt, indexx) => {
this.data.fService.getAddressPoint(elementt).subscribe(sbed => {
const newRoute = {
addressPointId: sbed.addressPointId,
municipalityId: sbed.municipalityId,
regionId: sbed.regionId,
rvId: element.rvId,
sequenceNumber: indexx,
settlementId: sbed.settlementId,
regionName: sbed.regionName,
municipalityName: sbed.municipalityName,
settlementName: sbed.settlementName,
description: sbed.description,
};
routeslocal.push({...newRoute});
});
});
this.requestVehicles.push(this.fb.group({
endDateTime: new Date(element.endDateTime),
garageId: element.garageId,
routes: new FormControl(routeslocal),
}));
});
});
});
});
}
}
The opening line, [] or [{}], is immediately drawn in the console.
In the case of [], there was nothing in the array at logging time, so the browser draw it as an empty array. But the data was present when you looked at it and clicked on the small triangle, later.
You can reproduce this behavior with this code in your console:
;(function(){ let arr=[]; setTimeout(()=>{ arr[0] = {b:3}; }); return arr;})()
So the difference you saw is related to the (a)synchronicity of array filling.
Vato, you has two functions in your service:getRequest(requestId) and getAddressPoint(requestVehicles). The idea is return a whole object. You can create the function in the own service or in the component. I'd like in the service, and that return an objservable. You must use forkJoin and swithMap So . It's for me impossible check if work
**Update, see the stackblitz
getFullRequest(id): Observable<any> {
return this.getRequest(id).pipe(
switchMap((request: any) => {
//here you has the request. We create an array of observables
return forkJoin(
request.requestVehicles.map(
(r: any) => this.getAddressPoint(r))).pipe(map((res: any[]) => {
res.forEach((x: any, index: number) => {
x.sequenceNumber = index
})
return {
statusId: request.statusID,
routes: res
}
})
)
}))
}
then, in your component
if (this.data.isEdit) {
this.Title = 'Edit';
this.data.fService.getFullRequest(this.data.requestId).subscribe(thisRequest => {
this.requestForm = this.fb.group({
statusId: thisRequest.status,
requestVehicles: thisRequest.routes
});
Update 2 briefly explain about switchMap and forkJoin.
When we make this.getRequest(id) we received in request an object. In this object we has in requestVehicles an array (can be an array of objects or an array of numbers -or strings-). With each element of this array we can make a call, But instead of make the calls one to one, we want to make all these together. For this we use forkJoin. forkJoin received an array of observables and, in subscribe received the response in an array
//if we has an observable like:
getValue(id:number):Observable<any>{
return of({one:id})
}
//and an array like
myArray=[1,2]
//and an array of response whe we can store the responses
response:any[]
//we can do
for (let id of myArray)
{
this.getValue(id).susbcribe(res=>{
this.response.push(res)
})
}
//or
observables:any[]
for (let id of myArray)
{
this.observables.push(this.getValue(id))
}
forkJoin(this.observables).subscribe((res;any[])=>{
//in res[0] we have the result of this.getValue(1)
//in res[1] we have the result of this.getValue(2)
//so, simply
this.response=res
})
//or in a compact way
//with each element of the array
observables=myArray.map(x=>this.getValues(x))
forkJoin(this.observables).subscribe((res;any[])=>{
this.response=res
})
Well, there are two problems more. We want add a new propertie "sequenceNumber" to all the response. So we use res.forEach(...) to add the property. And we want return an object with somes properties of our original request (statusID) and in "routes" the array with the response. So we use map to transform the response. In our simple example above
//not return simple {one:1}
//return {id:1,one:1}
getResponse(2).pipe.map(res=>{
return {
id:1,
one:res.one
}
}
While learning NodeJS, I've been battling to write a more concise logic to this code block (see below) that could either introduce recursion or make use of ES6 methods to provide more elegance and better readability.
I'm bothered by the nesting happening on the for of loops
Thoughts?
export function pleaseRefactorMe(measures, metrics, stats) {
if (!Array.isArray(metrics)) metrics = [metrics] //=> returns array [ 'temperature' ]
if (!Array.isArray(stats)) stats = [stats] //> returns array [ 'min', 'max', 'average' ]
let statistics = []
/**** refactor opportunity for nested for of loops ****/
for (let metric of metrics) {
for (let stat of stats) {
try {
let value = calculateStatsForMetric(stat, metric, measure)
if (value) {
statistics.push({
metric: metric,
stat: stat,
value: value
})
}
} catch (err) {
return err
}
}
}
return statistics
}
First, always pass arrays in, methods usually shouldn't do this sort of input validation in JavaScript. Also don't throw in calculateStatsForMetric, if you have throwing code there wrap it in a try/catch and return a falsey value.
Now, you can use higher order array methods like flatMap and map:
Take each metric
For each metric
Take each stat (this calls for a flatMap on a map)
Calculate a function on it
Keep truthy values (this calls for a filter)
Or in code:
export const refactored = (measure, metrics, stats) =>
metrics.flatMap(metric => stats.map(stat => ({
metric,
stat,
value: calculateStatsForMetric(stat, metric, measure)
}))).filter(o => o.value);
A simple approach would be to use forEach -
let statistics = [];
metrics.forEach(m => {
stats.forEach(s => {
let value = calculateStatsForMetric(s, m, measures);
if (value) {
statistics.push({
metric: m,
stat: s,
value: value
});
}
});
});
Input: an array of username strings
Needed output: an array of Javascript Objects that correspond to each username in the input. The properties for these JS objects is to be built from two API calls for each username (I am using $.getJSON calls. Suggestions welcome).
I have an array of usernames for the Twitch API:
let users = ["OgamingSC2", "storbeck", "comster404"] // actual list is longer
I want to use the Array.prototype.map() higher order function to create an array of objects like
let userObjects = [ {...}, {...}, {...}, ... ]
where each object looks like:
{
username: 'storbeck' // <-- Added by me
stream: null, // <-- Added by first API call
_links: { // <-- Added by first API call
self:'https://api.twitch.tv/kraken/streams/storbeck',
channel:'https://api.twitch.tv/kraken/channels/storbeck'
}
logo: 'http:// ... png' // <-- Added by second API call
}
These are the two API call functions that return the $.getJSON Promises:
let getStreamInfo = (username) => {
return $.getJSON('https://api.twitch.tv/kraken/streams/'+username+'?callback=?')
.then((x) => x) // should I include this then?
}
let getUserInfo = (twitchObject) => {
return $.getJSON('https://api.twitch.tv/kraken/users/'+ twitchObject.user )
}
What I have so far in my code, which isn't resulting in the intended objects is:
let userObjects = users.map((user)=>{
return getStreamInfo(user)
.done((data) => {
let result = {
username: user,
data: data
}
console.log(JSON.stringify(result)) // prints out the intended object so far
return result
})
})
Now when I print out the contents of userObjects, I get:
"{}"
"{}"
"{}"
// ... and so on
Going further, I'd like to chain userObjects and add more to each JS object from whatever I get in the getUserInfo function.
I'd like to go into how this can be done with functional Javascript, but this isn't necessary.
You are on the right way, you need only small edit on your functions.
let getStreamInfo = (username) => {
return $.getJSON('https://api.twitch.tv/kraken/streams/'+username+'?callback=?');
}
let getUserInfo = (user) => {
return $.getJSON('https://api.twitch.tv/kraken/users/'+ user);
}
let userObjects = [];
The core function instead needs Promise synchronization:
users.map((user)=>{
Promise.all(getStreamInfo(user), getUserInfo(user)).then((data)=>{
let obj = {
username: user,
stream: data[0].stream,
_links: data[0]._links,
logo: data[1].logo
}
userObjects.push(obj);
});
});